Android Html解析
前編のAndroid SpannablesStringの浅い分析ではhtmlを用いてテキスト処理の効果を実現した.当時の設定部分のコードは以下の通りです.
ここでは,一部の文字の表示色を変更するとともに,他の一部の内容にクリックイベントの処理を追加し,下線を加えた.今日はHtmlコードの解析を見てみましょう.
かいせきてつづき
上のコードで設定時にHtmlを呼び出しました.fromHtml()関数は,この関数からhtmlコンテンツをTextViewに解析して示すことができるコンテンツであることが分かる.ここから、このようなことをしたかどうかを見てみましょう.解析できる内容はどんなものですか.
この関数を呼び出す前に、まずHtmlのコンストラクション関数private Html()を見てみましょう.privateによって修飾されているのが見えます.外部ではHtmlインスタンスを構築できないことを示しています.コードにはインスタンスを返す場所もないので、ここでの使用方法は静的な方法で呼び出されています.
fromHtml()関数を見てみましょう.
Html.fromHtml関数は、3つのパラメータのromHtml(String source,ImageGetter imageGetter,TagHandler tagHandler)を呼び出し続け、パラメータはそれぞれデータソース、ピクチャ処理、tag処理である.まず、orgにあるParserの例を構築する.ccil.cowan.tagsoupパッケージの下で、私の地元のコードはナビゲートできません.ここでは、このコードの接続Parserソースコードをあげます.
ここではTagSoupについて追加します.彼の公式紹介を見てみましょう.
This is the home page of TagSoup, a SAX-compliant parser written in Java that, instead of parsing well-formed or valid XML, parses HTML as it is found in the wild: poor, nasty and brutish, though quite often far from short. TagSoup is designed for people who have to process this stuff using some semblance of a rational application design. By providing a SAX interface, it allows standard XML tools to be applied to even the worst HTML. TagSoup also includes a command-line processor that reads HTML files and can generate either clean HTML or well-formed XML that is a close approximation to XHTML.
大体の意味は次の通りです.
TagSoupはJava言語でHtmlを解析するツールを作成し、SAXエンジンを通じて構造が悪く、狂ったHTMLドキュメントを解析します.TagSoupは、HTMLドキュメントを構造の良いXMLドキュメントに変換し、開発者が取得したHTMLドキュメントを解析するなどの操作を容易にすることができます.同時にTagSoupはコマンドラインプログラムを提供し、TagSoupを実行してHTMLドキュメントを解析することができます.
Parserを構築してParserを呼び出す.setProperty関数は、schemaPropertyに伝わります.ここは文字列で、HTML Schemaオブジェクトにも伝わります.HTML SchemaオブジェクトにはHTMLのすべての属性ノードが羅列されています.HTML SchemaもTagSoupに属しています.ここではTagSoupについてあまり紹介しません.彼が何をしているのか知っていればいいです.
最後にHtmlToSpannedConverterインスタンスを構築し、上に渡されたパラメータ、データソース、imageGetter、tagHandler、parserを入力し、最後にHtmlToSpannedConverterのconvert関数を呼び出した.ここではこのクラス名を見て,主にHtmlコンテンツをSpanオブジェクトに変換する操作を大まかに知ることができる.ここでは前編に戻りますが、最終的にはSpanタイプが扱われています.
ここではまずHtmlToSpannedConverterの構造関数を見てみましょう.
入力されたパラメータを対応する変数に割り当てると同時に、StringBuilderタイプのSpannablesStringBuilderオブジェクトが構築されます.StringBuilderは主に文字列を接続し、不要なスペースの浪費を減らします.SpannablesStringBuilderはもちろんSpannablesStringに接続します.SpannablesStringの主な内容は前のAndroid SpannablesStringの浅い分析を見て、convert関数が何をしたかを見続けましょう.
ここでまずmReaderを呼び出します.setContentHandler関数、mReaderは前述のParserインスタンスであり、その後mReaderを呼び出す.parse関数は、入力されたsourceをInputSourceオブジェクトとして構築します.私たちはまずparseオブジェクトを見に行って、それから下を見ます.
まずsetUpを呼び出し、ここでは主にいくつかの付与、初期化操作を行います.次に入力されたInputSourceオブジェクトをReaderオブジェクトに変換し、ContentHanderのstartDocumentを呼び出します.ここではHtmlToSpannedConverterのstartDocumentが呼び出されます.空の関数で何もしていないことがわかります.ここでは主に最も主要な部分を見てみましょうscan(r,this)、ここではコードを見ないで、彼は主に以下の操作をしました.まずtheScannerはHTML Scannerタイプであるため、このscanは呼び出されたHTML Scannerのscan関数であり、ReaderとScanHanderに伝わり、ScanHanderはParserで実現される. scanでは各文字を読み出し、特殊文字を除去し、statetableテーブルに従って各文字を処理する .は、いくつかの文字に遭遇したときにsaveメソッド処理を呼び出し、ここでは主に入力bufferを表示するたびに、20文字以上が処理される.ScanHanderのpcdata関数を呼び出します. pcdata関数は空白文字を処理し、その後rectify関数を呼び出し、関数名から修正の意味であることがわかる. rectify関数ではElementチェーンテーブルが1つ処理されるため、複数回処理され、最後にrestartまたはpush関数が呼び出され、restartでもpush関数が呼び出され続けます. push関数でやっと属性のtheContentHandlerを見て、theContentHandlerを呼び出しました.startElement(namespace, localName, name, e.atts());ここではHtmlToSpannedConverterで実装されているので、ここで実際に呼び出されているのはHtmlToSpannedConverterのstartElement関数であり、startElement関数はhandleStartTag関数を呼び出し続けている.
handleStartTag関数を見てみましょう.
上記のコードからもhtmlのラベルを解析できることが分かるが,ここではすべてのElementのstartラベルのみを処理し,endラベルはまだ処理されていないが,startは主にtextにSpanを設定し,初期と終了までは同じで,空のクラスを設定し,識別対象としたが,ラベルp,divについては,imgなどは異なる設定をしており、特にimgでは主に設定置換アイコンが呼び出されており、この機能は前のImageGetterで実現されている.
上はstartラベルを作っただけで、endラベルはまた上を作って、前にrectifyがpushを呼び出してスタックを押さえたと言っていましたが、当時は他のコードを省略していました.ここではもう一つの処理をして、一つのElementスキャンが終わった後にpop出スタックを行って、popの中でElendement関数を呼び出して、ここで実際にHtmlToSpannedConverterのElendement関数を呼び出しました.endElement関数ではhandleEndTag関数の呼び出しが続行されます.
handleEndTag関数を見てみましょう.
ここではhandleStartTagとペアになって処理し,主に置換処理を行い,statに入力されたクラス識別に基づいてspanオブジェクトを再設定する.
ここでkindは前のmark,replが置き換えられたspanオブジェクトであり,ここでは主に以下のspanオブジェクトを用いることができる:StyleSpan,RelativeSizeSpan,QuoteSpan,UnderlineSpan,SuperscriptSpan.彼がSpannablesStringより処理できるものが少ないのが見えますが、ここではheaderの中で2つのspan処理が行われているのが見えます.最後に、すべてのノードが一致していない場合、自分でmTagHandlerを実現した場合は、mTagHandlerを使用して処理します.
ここでconvert関数に戻り、parse処理が完了した後、mSpannablesStringBuilderの処理を継続し、設定されたspanをループ処理し、''改行を無視し、spanのstartがendと同じ位置にある場合、ノードにコンテンツ処理がないことを示し、span removeを削除します.最後に、処理が完了したSpannablesStringBuilderをTextViewに戻して表示します.
ぎゃくかい
上でhtmlをSpannablesStringBuilderに変換し、HtmlはSpannablesStringBuilderコンテンツをhtmlコンテンツに変換することもできます.ここでは主にtoHtml関数を呼び出します.
toHtml関数ではwithinHtml関数が呼び出され、withinHtml関数ではspanオブジェクトがループ処理されます.AlignmentSpanオブジェクトの場合、外側のレイヤdivがネストされ、その後withinDivが呼び出されて処理が続行されます.
withinDivでテキストの処理を続行します.最終的にはwithinParagraphを呼び出してテキストを処理します.withinParagraphは、対応するspanオブジェクトを処理します.
ここでは異なるspanに基づいて対応するhtmlコンテンツを生成し,最後にhtml戻りを生成する.
まとめ
ここではプロセス全体を初歩的に解析しただけで、TagSoupのように、自分でコードを見ることができる内容がたくさんあります.TagSoupが狂ったhtmlの内容をどのように解析しているかを見てみましょう.
一般的に簡単な効果は、htmlを使えばいいのですが、相対的にコード量が少なく、htmlを知っている人はどのような効果を実現する必要があるのか、複雑な効果や機能があれば、あるいは同じテキストに複数の効果と機能が必要であれば、SpannablesStringを採用して実現する必要があります.実際の開発では需要に応じて対応する効果を実現する必要がある.
private void setText() {
String originText = "# # Android N (Developer Preview)";
String effect1 = "<font color='#FF0000'># #</font> <br> Android " +
"N <a href='http://developer.android.com/index.html'>(Developer Preview)</a>";
String effect2 = "<font color='#303F9F'># #</font> Android " +
"N <a href='http://developer.android.com/index.html'>(Developer Preview)</a>";
StringBuilder sb = new StringBuilder(originText);
sb.append("<br><br><br><br>");
sb.append(effect1);
sb.append("<br><br><br><br>");
sb.append(effect2);
textView.setText(Html.fromHtml(sb.toString()));
textView.setMovementMethod(LinkMovementMethod.getInstance());
}
ここでは,一部の文字の表示色を変更するとともに,他の一部の内容にクリックイベントの処理を追加し,下線を加えた.今日はHtmlコードの解析を見てみましょう.
かいせきてつづき
上のコードで設定時にHtmlを呼び出しました.fromHtml()関数は,この関数からhtmlコンテンツをTextViewに解析して示すことができるコンテンツであることが分かる.ここから、このようなことをしたかどうかを見てみましょう.解析できる内容はどんなものですか.
この関数を呼び出す前に、まずHtmlのコンストラクション関数private Html()を見てみましょう.privateによって修飾されているのが見えます.外部ではHtmlインスタンスを構築できないことを示しています.コードにはインスタンスを返す場所もないので、ここでの使用方法は静的な方法で呼び出されています.
fromHtml()関数を見てみましょう.
public static Spanned fromHtml(String source) {
return fromHtml(source, null, null);
}
public static Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler) {
Parser parser = new Parser();
try {
parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
} catch (org.xml.sax.SAXNotRecognizedException e) {
// Should not happen.
throw new RuntimeException(e);
} catch (org.xml.sax.SAXNotSupportedException e) {
// Should not happen.
throw new RuntimeException(e);
}
HtmlToSpannedConverter converter =
new HtmlToSpannedConverter(source, imageGetter, tagHandler, parser);
return converter.convert();
}
Html.fromHtml関数は、3つのパラメータのromHtml(String source,ImageGetter imageGetter,TagHandler tagHandler)を呼び出し続け、パラメータはそれぞれデータソース、ピクチャ処理、tag処理である.まず、orgにあるParserの例を構築する.ccil.cowan.tagsoupパッケージの下で、私の地元のコードはナビゲートできません.ここでは、このコードの接続Parserソースコードをあげます.
ここではTagSoupについて追加します.彼の公式紹介を見てみましょう.
This is the home page of TagSoup, a SAX-compliant parser written in Java that, instead of parsing well-formed or valid XML, parses HTML as it is found in the wild: poor, nasty and brutish, though quite often far from short. TagSoup is designed for people who have to process this stuff using some semblance of a rational application design. By providing a SAX interface, it allows standard XML tools to be applied to even the worst HTML. TagSoup also includes a command-line processor that reads HTML files and can generate either clean HTML or well-formed XML that is a close approximation to XHTML.
大体の意味は次の通りです.
TagSoupはJava言語でHtmlを解析するツールを作成し、SAXエンジンを通じて構造が悪く、狂ったHTMLドキュメントを解析します.TagSoupは、HTMLドキュメントを構造の良いXMLドキュメントに変換し、開発者が取得したHTMLドキュメントを解析するなどの操作を容易にすることができます.同時にTagSoupはコマンドラインプログラムを提供し、TagSoupを実行してHTMLドキュメントを解析することができます.
Parserを構築してParserを呼び出す.setProperty関数は、schemaPropertyに伝わります.ここは文字列で、HTML Schemaオブジェクトにも伝わります.HTML SchemaオブジェクトにはHTMLのすべての属性ノードが羅列されています.HTML SchemaもTagSoupに属しています.ここではTagSoupについてあまり紹介しません.彼が何をしているのか知っていればいいです.
最後にHtmlToSpannedConverterインスタンスを構築し、上に渡されたパラメータ、データソース、imageGetter、tagHandler、parserを入力し、最後にHtmlToSpannedConverterのconvert関数を呼び出した.ここではこのクラス名を見て,主にHtmlコンテンツをSpanオブジェクトに変換する操作を大まかに知ることができる.ここでは前編に戻りますが、最終的にはSpanタイプが扱われています.
ここではまずHtmlToSpannedConverterの構造関数を見てみましょう.
public HtmlToSpannedConverter(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler,
Parser parser) {
mSource = source;
mSpannableStringBuilder = new SpannableStringBuilder();
mImageGetter = imageGetter;
mTagHandler = tagHandler;
mReader = parser;
}
入力されたパラメータを対応する変数に割り当てると同時に、StringBuilderタイプのSpannablesStringBuilderオブジェクトが構築されます.StringBuilderは主に文字列を接続し、不要なスペースの浪費を減らします.SpannablesStringBuilderはもちろんSpannablesStringに接続します.SpannablesStringの主な内容は前のAndroid SpannablesStringの浅い分析を見て、convert関数が何をしたかを見続けましょう.
public Spanned convert() {
mReader.setContentHandler(this);
try {
mReader.parse(new InputSource(new StringReader(mSource)));
} catch (IOException e) {
// We are reading from a string. There should not be IO problems.
throw new RuntimeException(e);
} catch (SAXException e) {
// TagSoup doesn't throw parse exceptions.
throw new RuntimeException(e);
}
// Fix flags and range for paragraph-type markup.
Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class);
for (int i = 0; i < obj.length; i++) {
int start = mSpannableStringBuilder.getSpanStart(obj[i]);
int end = mSpannableStringBuilder.getSpanEnd(obj[i]);
// If the last line of the range is blank, back off by one.
if (end - 2 >= 0) {
if (mSpannableStringBuilder.charAt(end - 1) == '
' &&
mSpannableStringBuilder.charAt(end - 2) == '
') {
end--;
}
}
if (end == start) {
mSpannableStringBuilder.removeSpan(obj[i]);
} else {
mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH);
}
}
return mSpannableStringBuilder;
}
ここでまずmReaderを呼び出します.setContentHandler関数、mReaderは前述のParserインスタンスであり、その後mReaderを呼び出す.parse関数は、入力されたsourceをInputSourceオブジェクトとして構築します.私たちはまずparseオブジェクトを見に行って、それから下を見ます.
public void parse(InputSource input) throws IOException, SAXException {
setup();
Reader r = getReader(input);
theContentHandler.startDocument();
theScanner.resetDocumentLocator(input.getPublicId(), input.getSystemId());
if (theScanner instanceof Locator) {
theContentHandler.setDocumentLocator((Locator) theScanner);
}
if (!(theSchema.getURI().equals("")))
theContentHandler.startPrefixMapping(theSchema.getPrefix(), theSchema.getURI());
theScanner.scan(r, this);
}
まずsetUpを呼び出し、ここでは主にいくつかの付与、初期化操作を行います.次に入力されたInputSourceオブジェクトをReaderオブジェクトに変換し、ContentHanderのstartDocumentを呼び出します.ここではHtmlToSpannedConverterのstartDocumentが呼び出されます.空の関数で何もしていないことがわかります.ここでは主に最も主要な部分を見てみましょうscan(r,this)、ここではコードを見ないで、彼は主に以下の操作をしました.
handleStartTag関数を見てみましょう.
private void handleStartTag(String tag, Attributes attributes) {
if (tag.equalsIgnoreCase("br")) {
// We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>
// so we can safely emite the linebreaks when we handle the close tag.
} else if (tag.equalsIgnoreCase("p")) {
handleP(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("div")) {
handleP(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("strong")) {
start(mSpannableStringBuilder, new Bold());
} else if (tag.equalsIgnoreCase("b")) {
start(mSpannableStringBuilder, new Bold());
} else if (tag.equalsIgnoreCase("em")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("cite")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("dfn")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("i")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("big")) {
start(mSpannableStringBuilder, new Big());
} else if (tag.equalsIgnoreCase("small")) {
start(mSpannableStringBuilder, new Small());
} else if (tag.equalsIgnoreCase("font")) {
startFont(mSpannableStringBuilder, attributes);
} else if (tag.equalsIgnoreCase("blockquote")) {
handleP(mSpannableStringBuilder);
start(mSpannableStringBuilder, new Blockquote());
} else if (tag.equalsIgnoreCase("tt")) {
start(mSpannableStringBuilder, new Monospace());
} else if (tag.equalsIgnoreCase("a")) {
startA(mSpannableStringBuilder, attributes);
} else if (tag.equalsIgnoreCase("u")) {
start(mSpannableStringBuilder, new Underline());
} else if (tag.equalsIgnoreCase("sup")) {
start(mSpannableStringBuilder, new Super());
} else if (tag.equalsIgnoreCase("sub")) {
start(mSpannableStringBuilder, new Sub());
} else if (tag.length() == 2 &&
Character.toLowerCase(tag.charAt(0)) == 'h' &&
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
handleP(mSpannableStringBuilder);
start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));
} else if (tag.equalsIgnoreCase("img")) {
startImg(mSpannableStringBuilder, attributes, mImageGetter);
} else if (mTagHandler != null) {
mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
}
}
上記のコードからもhtmlのラベルを解析できることが分かるが,ここではすべてのElementのstartラベルのみを処理し,endラベルはまだ処理されていないが,startは主にtextにSpanを設定し,初期と終了までは同じで,空のクラスを設定し,識別対象としたが,ラベルp,divについては,imgなどは異なる設定をしており、特にimgでは主に設定置換アイコンが呼び出されており、この機能は前のImageGetterで実現されている.
private static void start(SpannableStringBuilder text, Object mark) {
int len = text.length();
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
}
上はstartラベルを作っただけで、endラベルはまた上を作って、前にrectifyがpushを呼び出してスタックを押さえたと言っていましたが、当時は他のコードを省略していました.ここではもう一つの処理をして、一つのElementスキャンが終わった後にpop出スタックを行って、popの中でElendement関数を呼び出して、ここで実際にHtmlToSpannedConverterのElendement関数を呼び出しました.endElement関数ではhandleEndTag関数の呼び出しが続行されます.
handleEndTag関数を見てみましょう.
private void handleEndTag(String tag) {
if (tag.equalsIgnoreCase("br")) {
handleBr(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("p")) {
handleP(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("div")) {
handleP(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("strong")) {
end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
} else if (tag.equalsIgnoreCase("b")) {
end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
} else if (tag.equalsIgnoreCase("em")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("cite")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("dfn")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("i")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("big")) {
end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));
} else if (tag.equalsIgnoreCase("small")) {
end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));
} else if (tag.equalsIgnoreCase("font")) {
endFont(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("blockquote")) {
handleP(mSpannableStringBuilder);
end(mSpannableStringBuilder, Blockquote.class, new QuoteSpan());
} else if (tag.equalsIgnoreCase("tt")) {
end(mSpannableStringBuilder, Monospace.class,
new TypefaceSpan("monospace"));
} else if (tag.equalsIgnoreCase("a")) {
endA(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("u")) {
end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
} else if (tag.equalsIgnoreCase("sup")) {
end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
} else if (tag.equalsIgnoreCase("sub")) {
end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());
} else if (tag.length() == 2 &&
Character.toLowerCase(tag.charAt(0)) == 'h' &&
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
handleP(mSpannableStringBuilder);
endHeader(mSpannableStringBuilder);
} else if (mTagHandler != null) {
mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
}
}
ここではhandleStartTagとペアになって処理し,主に置換処理を行い,statに入力されたクラス識別に基づいてspanオブジェクトを再設定する.
private static void end(SpannableStringBuilder text, Class kind, Object repl) {
int len = text.length();
Object obj = getLast(text, kind);
int where = text.getSpanStart(obj);
text.removeSpan(obj);
if (where != len) {
text.setSpan(repl, where, len,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
ここでkindは前のmark,replが置き換えられたspanオブジェクトであり,ここでは主に以下のspanオブジェクトを用いることができる:StyleSpan,RelativeSizeSpan,QuoteSpan,UnderlineSpan,SuperscriptSpan.彼がSpannablesStringより処理できるものが少ないのが見えますが、ここではheaderの中で2つのspan処理が行われているのが見えます.最後に、すべてのノードが一致していない場合、自分でmTagHandlerを実現した場合は、mTagHandlerを使用して処理します.
ここでconvert関数に戻り、parse処理が完了した後、mSpannablesStringBuilderの処理を継続し、設定されたspanをループ処理し、''改行を無視し、spanのstartがendと同じ位置にある場合、ノードにコンテンツ処理がないことを示し、span removeを削除します.最後に、処理が完了したSpannablesStringBuilderをTextViewに戻して表示します.
ぎゃくかい
上でhtmlをSpannablesStringBuilderに変換し、HtmlはSpannablesStringBuilderコンテンツをhtmlコンテンツに変換することもできます.ここでは主にtoHtml関数を呼び出します.
public static String toHtml(Spanned text) {
StringBuilder out = new StringBuilder();
withinHtml(out, text);
return out.toString();
}
private static void withinHtml(StringBuilder out, Spanned text) {
int len = text.length();
int next;
for (int i = 0; i < text.length(); i = next) {
next = text.nextSpanTransition(i, len, ParagraphStyle.class);
ParagraphStyle[] style = text.getSpans(i, next, ParagraphStyle.class);
String elements = " ";
boolean needDiv = false;
for(int j = 0; j < style.length; j++) {
if (style[j] instanceof AlignmentSpan) {
Layout.Alignment align =
((AlignmentSpan) style[j]).getAlignment();
needDiv = true;
if (align == Layout.Alignment.ALIGN_CENTER) {
elements = "align=\"center\" " + elements;
} else if (align == Layout.Alignment.ALIGN_OPPOSITE) {
elements = "align=\"right\" " + elements;
} else {
elements = "align=\"left\" " + elements;
}
}
}
if (needDiv) {
out.append("<div ").append(elements).append(">");
}
withinDiv(out, text, i, next);
if (needDiv) {
out.append("</div>");
}
}
}
toHtml関数ではwithinHtml関数が呼び出され、withinHtml関数ではspanオブジェクトがループ処理されます.AlignmentSpanオブジェクトの場合、外側のレイヤdivがネストされ、その後withinDivが呼び出されて処理が続行されます.
private static void withinDiv(StringBuilder out, Spanned text, int start, int end) {
int next;
for (int i = start; i < end; i = next) {
next = text.nextSpanTransition(i, end, QuoteSpan.class);
QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class);
for (QuoteSpan quote : quotes) {
out.append("<blockquote>");
}
withinBlockquote(out, text, i, next);
for (QuoteSpan quote : quotes) {
out.append("</blockquote>
");
}
}
}
withinDivでテキストの処理を続行します.最終的にはwithinParagraphを呼び出してテキストを処理します.withinParagraphは、対応するspanオブジェクトを処理します.
private static boolean withinParagraph(StringBuilder out, Spanned text, int start, int end, int nl, boolean last) {
int next;
for (int i = start; i < end; i = next) {
next = text.nextSpanTransition(i, end, CharacterStyle.class);
CharacterStyle[] style = text.getSpans(i, next, CharacterStyle.class);
for (int j = 0; j < style.length; j++) {
if (style[j] instanceof StyleSpan) {
int s = ((StyleSpan) style[j]).getStyle();
if ((s & Typeface.BOLD) != 0) {
out.append("<b>");
}
if ((s & Typeface.ITALIC) != 0) {
out.append("<i>");
}
}
if (style[j] instanceof TypefaceSpan) {
String s = ((TypefaceSpan) style[j]).getFamily();
if ("monospace".equals(s)) {
out.append("<tt>");
}
}
if (style[j] instanceof SuperscriptSpan) {
out.append("<sup>");
}
if (style[j] instanceof SubscriptSpan) {
out.append("<sub>");
}
if (style[j] instanceof UnderlineSpan) {
out.append("<u>");
}
if (style[j] instanceof StrikethroughSpan) {
out.append("<strike>");
}
if (style[j] instanceof URLSpan) {
out.append("<a href=\"");
out.append(((URLSpan) style[j]).getURL());
out.append("\">");
}
if (style[j] instanceof ImageSpan) {
out.append("<img src=\"");
out.append(((ImageSpan) style[j]).getSource());
out.append("\">");
i = next;
}
if (style[j] instanceof AbsoluteSizeSpan) {
out.append("<font size =\"");
out.append(((AbsoluteSizeSpan) style[j]).getSize() / 6);
out.append("\">");
}
if (style[j] instanceof ForegroundColorSpan) {
out.append("<font color =\"#");
String color = Integer.toHexString(((ForegroundColorSpan)style[j]).getForegroundColor() + 0x01000000);
while (color.length() < 6) {
color = "0" + color;
}
out.append(color);
out.append("\">");
}
}
withinStyle(out, text, i, next);
for (int j = style.length - 1; j >= 0; j--) {
if (style[j] instanceof ForegroundColorSpan) {
out.append("</font>");
}
if (style[j] instanceof AbsoluteSizeSpan) {
out.append("</font>");
}
if (style[j] instanceof URLSpan) {
out.append("</a>");
}
if (style[j] instanceof StrikethroughSpan) {
out.append("</strike>");
}
if (style[j] instanceof UnderlineSpan) {
out.append("</u>");
}
if (style[j] instanceof SubscriptSpan) {
out.append("</sub>");
}
if (style[j] instanceof SuperscriptSpan) {
out.append("</sup>");
}
if (style[j] instanceof TypefaceSpan) {
String s = ((TypefaceSpan) style[j]).getFamily();
if (s.equals("monospace")) {
out.append("</tt>");
}
}
if (style[j] instanceof StyleSpan) {
int s = ((StyleSpan) style[j]).getStyle();
if ((s & Typeface.BOLD) != 0) {
out.append("</b>");
}
if ((s & Typeface.ITALIC) != 0) {
out.append("</i>");
}
}
}
}
if (nl == 1) {
out.append("<br>
");
return false;
} else {
for (int i = 2; i < nl; i++) {
out.append("<br>");
}
return !last;
}
}
private static void withinStyle(StringBuilder out, CharSequence text, int start, int end) {
for (int i = start; i < end; i++) {
char c = text.charAt(i);
if (c == '<') {
out.append("<");
} else if (c == '>') {
out.append(">");
} else if (c == '&') {
out.append("&");
} else if (c >= 0xD800 && c <= 0xDFFF) {
if (c < 0xDC00 && i + 1 < end) {
char d = text.charAt(i + 1);
if (d >= 0xDC00 && d <= 0xDFFF) {
i++;
int codepoint = 0x010000 | (int) c - 0xD800 << 10 | (int) d - 0xDC00;
out.append("&#").append(codepoint).append(";");
}
}
} else if (c > 0x7E || c < ' ') {
out.append("&#").append((int) c).append(";");
} else if (c == ' ') {
while (i + 1 < end && text.charAt(i + 1) == ' ') {
out.append(" ");
i++;
}
out.append(' ');
} else {
out.append(c);
}
}
}
ここでは異なるspanに基づいて対応するhtmlコンテンツを生成し,最後にhtml戻りを生成する.
まとめ
ここではプロセス全体を初歩的に解析しただけで、TagSoupのように、自分でコードを見ることができる内容がたくさんあります.TagSoupが狂ったhtmlの内容をどのように解析しているかを見てみましょう.
一般的に簡単な効果は、htmlを使えばいいのですが、相対的にコード量が少なく、htmlを知っている人はどのような効果を実現する必要があるのか、複雑な効果や機能があれば、あるいは同じテキストに複数の効果と機能が必要であれば、SpannablesStringを採用して実現する必要があります.実際の開発では需要に応じて対応する効果を実現する必要がある.