177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao/** 277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Copyright (c) 2014, Google Inc. 377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * 477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Licensed under the Apache License, Version 2.0 (the "License"); 577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * you may not use this file except in compliance with the License. 677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * You may obtain a copy of the License at 777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * 877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * http://www.apache.org/licenses/LICENSE-2.0 977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * 1077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Unless required by applicable law or agreed to in writing, software 1177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * distributed under the License is distributed on an "AS IS" BASIS, 1277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * See the License for the specific language governing permissions and 1477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * limitations under the License. 1577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao */ 1677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 1777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caopackage com.android.mail.utils; 1877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 1977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.graphics.Color; 2077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.graphics.Typeface; 2177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.SpannableStringBuilder; 2277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.Spanned; 23783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Caoimport android.text.style.AbsoluteSizeSpan; 2477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.ForegroundColorSpan; 2577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.QuoteSpan; 2677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.StyleSpan; 2777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.TypefaceSpan; 2877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.URLSpan; 2977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.UnderlineSpan; 3077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 3177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.android.mail.analytics.AnalyticsTimer; 32e26a92adf3f39d36995c67d31fe8eae3481f22d5Andy Huangimport com.google.android.mail.common.base.CharMatcher; 336c369ca7cd069c62ae839125088dffafb688631fJin Caoimport com.google.android.mail.common.html.parser.HTML; 346c369ca7cd069c62ae839125088dffafb688631fJin Caoimport com.google.android.mail.common.html.parser.HTML4; 3577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.google.android.mail.common.html.parser.HtmlDocument; 3677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.google.android.mail.common.html.parser.HtmlTree; 3777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.google.common.collect.Lists; 3877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 3977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport java.util.LinkedList; 4077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 4177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caopublic class HtmlUtils { 4277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 431d70245949b109f2e66779d0038b1f6bfd4f102dJin Cao static final String LOG_TAG = LogTag.getLogTag(); 441d70245949b109f2e66779d0038b1f6bfd4f102dJin Cao 4577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao /** 4677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Use our custom SpannedConverter to process the HtmlNode results from HtmlTree. 4777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * @param html 489ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang * @return processed HTML as a Spanned 4977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao */ 509ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang public static Spanned htmlToSpan(String html, HtmlTree.ConverterFactory factory) { 5177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao AnalyticsTimer.getInstance().trackStart(AnalyticsTimer.COMPOSE_HTML_TO_SPAN); 5277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // Get the html "tree" 5377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao final HtmlTree htmlTree = com.android.mail.utils.Utils.getHtmlTree(html); 549ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang htmlTree.setConverterFactory(factory); 5577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao final Spanned spanned = htmlTree.getSpanned(); 5677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao AnalyticsTimer.getInstance().logDuration(AnalyticsTimer.COMPOSE_HTML_TO_SPAN, true, 5777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao "compose", "html_to_span", null); 582871511a6076f92b4c7d017f0d7c1b8c4ec69a05Jin Cao LogUtils.i(LOG_TAG, "htmlToSpan completed, input: %d, result: %d", html.length(), 591d70245949b109f2e66779d0038b1f6bfd4f102dJin Cao spanned.length()); 6077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao return spanned; 6177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 6277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 6377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao /** 6477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Class that handles converting the html into a Spanned. 6577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * This class will only handle a subset of the html tags. Below is the full list: 6677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - bold 6777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - italic 6877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - underline 6977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - font size 7077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - font color 7177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - font face 7277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - a 7377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - blockquote 7477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - p 7577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * - div 7677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao */ 779ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang public static class SpannedConverter implements HtmlTree.Converter<Spanned> { 78783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao // Pinto normal text size is 2 while normal for AbsoluteSizeSpan is 12. 79783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao // So 6 seems to be the magic number here. Html.toHtml also uses 6 as divider. 80783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao private static final int WEB_TO_ANDROID_SIZE_MULTIPLIER = 6; 81783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao 829ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang protected final SpannableStringBuilder mBuilder = new SpannableStringBuilder(); 8377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao private final LinkedList<TagWrapper> mSeenTags = Lists.newLinkedList(); 8477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 857b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao private final HtmlTree.DefaultPlainTextConverter mTextConverter = 867b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao new HtmlTree.DefaultPlainTextConverter(); 877b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao private int mTextConverterIndex = 0; 88e26a92adf3f39d36995c67d31fe8eae3481f22d5Andy Huang 8977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao @Override 9077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao public void addNode(HtmlDocument.Node n, int nodeNum, int endNum) { 917b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao // Feed it into the plain text converter 927b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao mTextConverter.addNode(n, nodeNum, endNum); 937b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao if (n instanceof HtmlDocument.Tag) { 9477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao handleStart((HtmlDocument.Tag) n); 9577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } else if (n instanceof HtmlDocument.EndTag) { 9677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao handleEnd((HtmlDocument.EndTag) n); 9777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 987b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao appendPlainTextFromConverter(); 997b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao } 1007b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao 1017b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao private void appendPlainTextFromConverter() { 1027b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao String textString = mTextConverter.getObject(); 1037b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao if (textString.length() > mTextConverterIndex) { 1047b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao mBuilder.append(textString.substring(mTextConverterIndex)); 1057b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao mTextConverterIndex = textString.length(); 1067b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao } 10777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 10877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 10977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao /** 11077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Helper function to handle start tag 11177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao */ 1129ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang protected void handleStart(HtmlDocument.Tag tag) { 11377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao if (!tag.isSelfTerminating()) { 11477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // Add to the stack of tags needing closing tag 1156c369ca7cd069c62ae839125088dffafb688631fJin Cao mSeenTags.push(new TagWrapper(tag, mBuilder.length())); 11677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 11777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 11877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 11977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao /** 12077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Helper function to handle end tag 12177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao */ 1229ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang protected void handleEnd(HtmlDocument.EndTag tag) { 12377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao TagWrapper lastSeen; 1246c369ca7cd069c62ae839125088dffafb688631fJin Cao HTML.Element element = tag.getElement(); 1256c369ca7cd069c62ae839125088dffafb688631fJin Cao while ((lastSeen = mSeenTags.poll()) != null && lastSeen.tag.getElement() != null && 1266c369ca7cd069c62ae839125088dffafb688631fJin Cao !lastSeen.tag.getElement().equals(element)) { } 12777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 12877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // Misformatted html, just ignore this tag 12977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao if (lastSeen == null) { 13077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao return; 13177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 13277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 1337b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao Object marker = null; 1346c369ca7cd069c62ae839125088dffafb688631fJin Cao if (HTML4.B_ELEMENT.equals(element)) { 13577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // BOLD 13677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao marker = new StyleSpan(Typeface.BOLD); 1376c369ca7cd069c62ae839125088dffafb688631fJin Cao } else if (HTML4.I_ELEMENT.equals(element)) { 13877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // ITALIC 13977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao marker = new StyleSpan(Typeface.ITALIC); 1406c369ca7cd069c62ae839125088dffafb688631fJin Cao } else if (HTML4.U_ELEMENT.equals(element)) { 14177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // UNDERLINE 14277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao marker = new UnderlineSpan(); 1436c369ca7cd069c62ae839125088dffafb688631fJin Cao } else if (HTML4.A_ELEMENT.equals(element)) { 14477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // A HREF 1456c369ca7cd069c62ae839125088dffafb688631fJin Cao HtmlDocument.TagAttribute attr = lastSeen.tag.getAttribute(HTML4.HREF_ATTRIBUTE); 14677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // Ignore this tag if it doesn't have a link 1476c369ca7cd069c62ae839125088dffafb688631fJin Cao if (attr == null) { 14877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao return; 14977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 1506c369ca7cd069c62ae839125088dffafb688631fJin Cao marker = new URLSpan(attr.getValue()); 1516c369ca7cd069c62ae839125088dffafb688631fJin Cao } else if (HTML4.BLOCKQUOTE_ELEMENT.equals(element)) { 15277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // BLOCKQUOTE 15377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao marker = new QuoteSpan(); 1546c369ca7cd069c62ae839125088dffafb688631fJin Cao } else if (HTML4.FONT_ELEMENT.equals(element)) { 15577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // FONT SIZE/COLOR/FACE, since this can insert more than one span 15677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // we special case it and return 15777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao handleFont(lastSeen); 15877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 15977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 16077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao final int start = lastSeen.startIndex; 16177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao final int end = mBuilder.length(); 1627b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao if (marker != null && start != end) { 16377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao mBuilder.setSpan(marker, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 16477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 16577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 16677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 16777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao /** 16877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Helper function to handle end font tags 16977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao */ 17077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao private void handleFont(TagWrapper wrapper) { 17177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao final int start = wrapper.startIndex; 17277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao final int end = mBuilder.length(); 17377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 17477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // check font color 1756c369ca7cd069c62ae839125088dffafb688631fJin Cao HtmlDocument.TagAttribute attr = wrapper.tag.getAttribute(HTML4.COLOR_ATTRIBUTE); 1766c369ca7cd069c62ae839125088dffafb688631fJin Cao if (attr != null) { 1776c369ca7cd069c62ae839125088dffafb688631fJin Cao int c = Color.parseColor(attr.getValue()); 17877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao if (c != -1) { 17977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao mBuilder.setSpan(new ForegroundColorSpan(c | 0xFF000000), start, end, 18077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 18177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 18277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 18377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 18477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // check font size 1856c369ca7cd069c62ae839125088dffafb688631fJin Cao attr = wrapper.tag.getAttribute(HTML4.SIZE_ATTRIBUTE); 1866c369ca7cd069c62ae839125088dffafb688631fJin Cao if (attr != null) { 1876c369ca7cd069c62ae839125088dffafb688631fJin Cao int i = Integer.parseInt(attr.getValue()); 18877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao if (i != -1) { 189783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao mBuilder.setSpan(new AbsoluteSizeSpan(i * WEB_TO_ANDROID_SIZE_MULTIPLIER, 190783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao true /* use dip */), start, end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 19177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 19277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 19377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 19477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao // check font typeface 1956c369ca7cd069c62ae839125088dffafb688631fJin Cao attr = wrapper.tag.getAttribute(HTML4.FACE_ATTRIBUTE); 1966c369ca7cd069c62ae839125088dffafb688631fJin Cao if (attr != null) { 1976c369ca7cd069c62ae839125088dffafb688631fJin Cao String[] families = attr.getValue().split(","); 19877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao for (String family : families) { 19977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao mBuilder.setSpan(new TypefaceSpan(family.trim()), start, end, 20077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 20177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 20277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 20377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 20477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 20577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao @Override 20677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao public int getPlainTextLength() { 20777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao return mBuilder.length(); 20877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 20977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 21077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao @Override 21177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao public Spanned getObject() { 21277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao return mBuilder; 21377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 21477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 21577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao private static class TagWrapper { 2166c369ca7cd069c62ae839125088dffafb688631fJin Cao final HtmlDocument.Tag tag; 21777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao final int startIndex; 21877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao 2196c369ca7cd069c62ae839125088dffafb688631fJin Cao TagWrapper(HtmlDocument.Tag tag, int startIndex) { 2206c369ca7cd069c62ae839125088dffafb688631fJin Cao this.tag = tag; 22177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao this.startIndex = startIndex; 22277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 22377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 22477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao } 22577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao} 226