Html.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.text; 18 19import org.ccil.cowan.tagsoup.HTMLSchema; 20import org.ccil.cowan.tagsoup.Parser; 21import org.xml.sax.Attributes; 22import org.xml.sax.ContentHandler; 23import org.xml.sax.InputSource; 24import org.xml.sax.Locator; 25import org.xml.sax.SAXException; 26import org.xml.sax.XMLReader; 27 28import android.content.res.Resources; 29import android.graphics.Typeface; 30import android.graphics.drawable.Drawable; 31import android.text.style.CharacterStyle; 32import android.text.style.ForegroundColorSpan; 33import android.text.style.ImageSpan; 34import android.text.style.ParagraphStyle; 35import android.text.style.QuoteSpan; 36import android.text.style.RelativeSizeSpan; 37import android.text.style.StrikethroughSpan; 38import android.text.style.StyleSpan; 39import android.text.style.SubscriptSpan; 40import android.text.style.SuperscriptSpan; 41import android.text.style.TypefaceSpan; 42import android.text.style.URLSpan; 43import android.text.style.UnderlineSpan; 44import com.android.internal.util.XmlUtils; 45 46import java.io.IOException; 47import java.io.StringReader; 48import java.nio.CharBuffer; 49 50/** 51 * This class processes HTML strings into displayable styled text. 52 * Not all HTML tags are supported. 53 */ 54public class Html { 55 /** 56 * Retrieves images for HTML <img> tags. 57 */ 58 public static interface ImageGetter { 59 /** 60 * This methos is called when the HTML parser encounters an 61 * <img> tag. The <code>source</code> argument is the 62 * string from the "src" attribute; the return value should be 63 * a Drawable representation of the image or <code>null</code> 64 * for a generic replacement image. Make sure you call 65 * setBounds() on your Drawable if it doesn't already have 66 * its bounds set. 67 */ 68 public Drawable getDrawable(String source); 69 } 70 71 /** 72 * Is notified when HTML tags are encountered that the parser does 73 * not know how to interpret. 74 */ 75 public static interface TagHandler { 76 /** 77 * This method will be called whenn the HTML parser encounters 78 * a tag that it does not know how to interpret. 79 */ 80 public void handleTag(boolean opening, String tag, 81 Editable output, XMLReader xmlReader); 82 } 83 84 private Html() { } 85 86 /** 87 * Returns displayable styled text from the provided HTML string. 88 * Any <img> tags in the HTML will display as a generic 89 * replacement image which your program can then go through and 90 * replace with real images. 91 * 92 * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. 93 */ 94 public static Spanned fromHtml(String source) { 95 return fromHtml(source, null, null); 96 } 97 98 /** 99 * Lazy initialization holder for HTML parser. This class will 100 * a) be preloaded by the zygote, or b) not loaded until absolutely 101 * necessary. 102 */ 103 private static class HtmlParser { 104 private static final HTMLSchema schema = new HTMLSchema(); 105 } 106 107 /** 108 * Returns displayable styled text from the provided HTML string. 109 * Any <img> tags in the HTML will use the specified ImageGetter 110 * to request a representation of the image (use null if you don't 111 * want this) and the specified TagHandler to handle unknown tags 112 * (specify null if you don't want this). 113 * 114 * <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. 115 */ 116 public static Spanned fromHtml(String source, ImageGetter imageGetter, 117 TagHandler tagHandler) { 118 Parser parser = new Parser(); 119 try { 120 parser.setProperty(Parser.schemaProperty, HtmlParser.schema); 121 } catch (org.xml.sax.SAXNotRecognizedException e) { 122 // Should not happen. 123 throw new RuntimeException(e); 124 } catch (org.xml.sax.SAXNotSupportedException e) { 125 // Should not happen. 126 throw new RuntimeException(e); 127 } 128 129 HtmlToSpannedConverter converter = 130 new HtmlToSpannedConverter(source, imageGetter, tagHandler, 131 parser); 132 return converter.convert(); 133 } 134 135 /** 136 * Returns an HTML representation of the provided Spanned text. 137 */ 138 public static String toHtml(Spanned text) { 139 StringBuilder out = new StringBuilder(); 140 int len = text.length(); 141 142 int next; 143 for (int i = 0; i < text.length(); i = next) { 144 next = text.nextSpanTransition(i, len, QuoteSpan.class); 145 QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class); 146 147 for (QuoteSpan quote: quotes) { 148 out.append("<blockquote>"); 149 } 150 151 withinBlockquote(out, text, i, next); 152 153 for (QuoteSpan quote: quotes) { 154 out.append("</blockquote>\n"); 155 } 156 } 157 158 return out.toString(); 159 } 160 161 private static void withinBlockquote(StringBuilder out, Spanned text, 162 int start, int end) { 163 out.append("<p>"); 164 165 int next; 166 for (int i = start; i < end; i = next) { 167 next = TextUtils.indexOf(text, '\n', i, end); 168 if (next < 0) { 169 next = end; 170 } 171 172 int nl = 0; 173 174 while (next < end && text.charAt(next) == '\n') { 175 nl++; 176 next++; 177 } 178 179 withinParagraph(out, text, i, next - nl, nl, next == end); 180 } 181 182 out.append("</p>\n"); 183 } 184 185 private static void withinParagraph(StringBuilder out, Spanned text, 186 int start, int end, int nl, 187 boolean last) { 188 int next; 189 for (int i = start; i < end; i = next) { 190 next = text.nextSpanTransition(i, end, CharacterStyle.class); 191 CharacterStyle[] style = text.getSpans(i, next, 192 CharacterStyle.class); 193 194 for (int j = 0; j < style.length; j++) { 195 if (style[j] instanceof StyleSpan) { 196 int s = ((StyleSpan) style[j]).getStyle(); 197 198 if ((s & Typeface.BOLD) != 0) { 199 out.append("<b>"); 200 } 201 if ((s & Typeface.ITALIC) != 0) { 202 out.append("<i>"); 203 } 204 } 205 if (style[j] instanceof TypefaceSpan) { 206 String s = ((TypefaceSpan) style[j]).getFamily(); 207 208 if (s.equals("monospace")) { 209 out.append("<tt>"); 210 } 211 } 212 if (style[j] instanceof SuperscriptSpan) { 213 out.append("<sup>"); 214 } 215 if (style[j] instanceof SubscriptSpan) { 216 out.append("<sub>"); 217 } 218 if (style[j] instanceof UnderlineSpan) { 219 out.append("<u>"); 220 } 221 if (style[j] instanceof StrikethroughSpan) { 222 out.append("<strike>"); 223 } 224 if (style[j] instanceof URLSpan) { 225 out.append("<a href=\""); 226 out.append(((URLSpan) style[j]).getURL()); 227 out.append("\">"); 228 } 229 if (style[j] instanceof ImageSpan) { 230 out.append("<img src=\""); 231 out.append(((ImageSpan) style[j]).getSource()); 232 out.append("\">"); 233 234 // Don't output the dummy character underlying the image. 235 i = next; 236 } 237 } 238 239 withinStyle(out, text, i, next); 240 241 for (int j = style.length - 1; j >= 0; j--) { 242 if (style[j] instanceof URLSpan) { 243 out.append("</a>"); 244 } 245 if (style[j] instanceof StrikethroughSpan) { 246 out.append("</strike>"); 247 } 248 if (style[j] instanceof UnderlineSpan) { 249 out.append("</u>"); 250 } 251 if (style[j] instanceof SubscriptSpan) { 252 out.append("</sub>"); 253 } 254 if (style[j] instanceof SuperscriptSpan) { 255 out.append("</sup>"); 256 } 257 if (style[j] instanceof TypefaceSpan) { 258 String s = ((TypefaceSpan) style[j]).getFamily(); 259 260 if (s.equals("monospace")) { 261 out.append("</tt>"); 262 } 263 } 264 if (style[j] instanceof StyleSpan) { 265 int s = ((StyleSpan) style[j]).getStyle(); 266 267 if ((s & Typeface.BOLD) != 0) { 268 out.append("</b>"); 269 } 270 if ((s & Typeface.ITALIC) != 0) { 271 out.append("</i>"); 272 } 273 } 274 } 275 } 276 277 String p = last ? "" : "</p>\n<p>"; 278 279 if (nl == 1) { 280 out.append("<br>\n"); 281 } else if (nl == 2) { 282 out.append(p); 283 } else { 284 for (int i = 2; i < nl; i++) { 285 out.append("<br>"); 286 } 287 288 out.append(p); 289 } 290 } 291 292 private static void withinStyle(StringBuilder out, Spanned text, 293 int start, int end) { 294 for (int i = start; i < end; i++) { 295 char c = text.charAt(i); 296 297 if (c == '<') { 298 out.append("<"); 299 } else if (c == '>') { 300 out.append(">"); 301 } else if (c == '&') { 302 out.append("&"); 303 } else if (c > 0x7E || c < ' ') { 304 out.append("&#" + ((int) c) + ";"); 305 } else if (c == ' ') { 306 while (i + 1 < end && text.charAt(i + 1) == ' ') { 307 out.append(" "); 308 i++; 309 } 310 311 out.append(' '); 312 } else { 313 out.append(c); 314 } 315 } 316 } 317} 318 319class HtmlToSpannedConverter implements ContentHandler { 320 321 private static final float[] HEADER_SIZES = { 322 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f, 323 }; 324 325 private String mSource; 326 private XMLReader mReader; 327 private SpannableStringBuilder mSpannableStringBuilder; 328 private Html.ImageGetter mImageGetter; 329 private Html.TagHandler mTagHandler; 330 331 public HtmlToSpannedConverter( 332 String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler, 333 Parser parser) { 334 mSource = source; 335 mSpannableStringBuilder = new SpannableStringBuilder(); 336 mImageGetter = imageGetter; 337 mTagHandler = tagHandler; 338 mReader = parser; 339 } 340 341 public Spanned convert() { 342 343 mReader.setContentHandler(this); 344 try { 345 mReader.parse(new InputSource(new StringReader(mSource))); 346 } catch (IOException e) { 347 // We are reading from a string. There should not be IO problems. 348 throw new RuntimeException(e); 349 } catch (SAXException e) { 350 // TagSoup doesn't throw parse exceptions. 351 throw new RuntimeException(e); 352 } 353 354 // Fix flags and range for paragraph-type markup. 355 Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class); 356 for (int i = 0; i < obj.length; i++) { 357 int start = mSpannableStringBuilder.getSpanStart(obj[i]); 358 int end = mSpannableStringBuilder.getSpanEnd(obj[i]); 359 360 // If the last line of the range is blank, back off by one. 361 if (end - 2 >= 0) { 362 if (mSpannableStringBuilder.charAt(end - 1) == '\n' && 363 mSpannableStringBuilder.charAt(end - 2) == '\n') { 364 end--; 365 } 366 } 367 368 if (end == start) { 369 mSpannableStringBuilder.removeSpan(obj[i]); 370 } else { 371 mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH); 372 } 373 } 374 375 return mSpannableStringBuilder; 376 } 377 378 private void handleStartTag(String tag, Attributes attributes) { 379 if (tag.equalsIgnoreCase("br")) { 380 // We don't need to handle this. TagSoup will ensure that there's a </br> for each <br> 381 // so we can safely emite the linebreaks when we handle the close tag. 382 } else if (tag.equalsIgnoreCase("p")) { 383 handleP(mSpannableStringBuilder); 384 } else if (tag.equalsIgnoreCase("div")) { 385 handleP(mSpannableStringBuilder); 386 } else if (tag.equalsIgnoreCase("em")) { 387 start(mSpannableStringBuilder, new Bold()); 388 } else if (tag.equalsIgnoreCase("b")) { 389 start(mSpannableStringBuilder, new Bold()); 390 } else if (tag.equalsIgnoreCase("strong")) { 391 start(mSpannableStringBuilder, new Italic()); 392 } else if (tag.equalsIgnoreCase("cite")) { 393 start(mSpannableStringBuilder, new Italic()); 394 } else if (tag.equalsIgnoreCase("dfn")) { 395 start(mSpannableStringBuilder, new Italic()); 396 } else if (tag.equalsIgnoreCase("i")) { 397 start(mSpannableStringBuilder, new Italic()); 398 } else if (tag.equalsIgnoreCase("big")) { 399 start(mSpannableStringBuilder, new Big()); 400 } else if (tag.equalsIgnoreCase("small")) { 401 start(mSpannableStringBuilder, new Small()); 402 } else if (tag.equalsIgnoreCase("font")) { 403 startFont(mSpannableStringBuilder, attributes); 404 } else if (tag.equalsIgnoreCase("blockquote")) { 405 handleP(mSpannableStringBuilder); 406 start(mSpannableStringBuilder, new Blockquote()); 407 } else if (tag.equalsIgnoreCase("tt")) { 408 start(mSpannableStringBuilder, new Monospace()); 409 } else if (tag.equalsIgnoreCase("a")) { 410 startA(mSpannableStringBuilder, attributes); 411 } else if (tag.equalsIgnoreCase("u")) { 412 start(mSpannableStringBuilder, new Underline()); 413 } else if (tag.equalsIgnoreCase("sup")) { 414 start(mSpannableStringBuilder, new Super()); 415 } else if (tag.equalsIgnoreCase("sub")) { 416 start(mSpannableStringBuilder, new Sub()); 417 } else if (tag.length() == 2 && 418 Character.toLowerCase(tag.charAt(0)) == 'h' && 419 tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { 420 handleP(mSpannableStringBuilder); 421 start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1')); 422 } else if (tag.equalsIgnoreCase("img")) { 423 startImg(mSpannableStringBuilder, attributes, mImageGetter); 424 } else if (mTagHandler != null) { 425 mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader); 426 } 427 } 428 429 private void handleEndTag(String tag) { 430 if (tag.equalsIgnoreCase("br")) { 431 handleBr(mSpannableStringBuilder); 432 } else if (tag.equalsIgnoreCase("p")) { 433 handleP(mSpannableStringBuilder); 434 } else if (tag.equalsIgnoreCase("div")) { 435 handleP(mSpannableStringBuilder); 436 } else if (tag.equalsIgnoreCase("em")) { 437 end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); 438 } else if (tag.equalsIgnoreCase("b")) { 439 end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); 440 } else if (tag.equalsIgnoreCase("strong")) { 441 end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); 442 } else if (tag.equalsIgnoreCase("cite")) { 443 end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); 444 } else if (tag.equalsIgnoreCase("dfn")) { 445 end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); 446 } else if (tag.equalsIgnoreCase("i")) { 447 end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); 448 } else if (tag.equalsIgnoreCase("big")) { 449 end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f)); 450 } else if (tag.equalsIgnoreCase("small")) { 451 end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f)); 452 } else if (tag.equalsIgnoreCase("font")) { 453 endFont(mSpannableStringBuilder); 454 } else if (tag.equalsIgnoreCase("blockquote")) { 455 handleP(mSpannableStringBuilder); 456 end(mSpannableStringBuilder, Blockquote.class, new QuoteSpan()); 457 } else if (tag.equalsIgnoreCase("tt")) { 458 end(mSpannableStringBuilder, Monospace.class, 459 new TypefaceSpan("monospace")); 460 } else if (tag.equalsIgnoreCase("a")) { 461 endA(mSpannableStringBuilder); 462 } else if (tag.equalsIgnoreCase("u")) { 463 end(mSpannableStringBuilder, Underline.class, new UnderlineSpan()); 464 } else if (tag.equalsIgnoreCase("sup")) { 465 end(mSpannableStringBuilder, Super.class, new SuperscriptSpan()); 466 } else if (tag.equalsIgnoreCase("sub")) { 467 end(mSpannableStringBuilder, Sub.class, new SubscriptSpan()); 468 } else if (tag.length() == 2 && 469 Character.toLowerCase(tag.charAt(0)) == 'h' && 470 tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { 471 handleP(mSpannableStringBuilder); 472 endHeader(mSpannableStringBuilder); 473 } else if (mTagHandler != null) { 474 mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader); 475 } 476 } 477 478 private static void handleP(SpannableStringBuilder text) { 479 int len = text.length(); 480 481 if (len >= 1 && text.charAt(len - 1) == '\n') { 482 if (len >= 2 && text.charAt(len - 2) == '\n') { 483 return; 484 } 485 486 text.append("\n"); 487 return; 488 } 489 490 if (len != 0) { 491 text.append("\n\n"); 492 } 493 } 494 495 private static void handleBr(SpannableStringBuilder text) { 496 text.append("\n"); 497 } 498 499 private static Object getLast(Spanned text, Class kind) { 500 /* 501 * This knows that the last returned object from getSpans() 502 * will be the most recently added. 503 */ 504 Object[] objs = text.getSpans(0, text.length(), kind); 505 506 if (objs.length == 0) { 507 return null; 508 } else { 509 return objs[objs.length - 1]; 510 } 511 } 512 513 private static void start(SpannableStringBuilder text, Object mark) { 514 int len = text.length(); 515 text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK); 516 } 517 518 private static void end(SpannableStringBuilder text, Class kind, 519 Object repl) { 520 int len = text.length(); 521 Object obj = getLast(text, kind); 522 int where = text.getSpanStart(obj); 523 524 text.removeSpan(obj); 525 526 if (where != len) { 527 text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 528 } 529 530 return; 531 } 532 533 private static void startImg(SpannableStringBuilder text, 534 Attributes attributes, Html.ImageGetter img) { 535 String src = attributes.getValue("", "src"); 536 Drawable d = null; 537 538 if (img != null) { 539 d = img.getDrawable(src); 540 } 541 542 if (d == null) { 543 d = Resources.getSystem(). 544 getDrawable(com.android.internal.R.drawable.unknown_image); 545 d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 546 } 547 548 int len = text.length(); 549 text.append("\uFFFC"); 550 551 text.setSpan(new ImageSpan(d, src), len, text.length(), 552 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 553 } 554 555 private static void startFont(SpannableStringBuilder text, 556 Attributes attributes) { 557 String color = attributes.getValue("", "color"); 558 String face = attributes.getValue("", "face"); 559 560 int len = text.length(); 561 text.setSpan(new Font(color, face), len, len, Spannable.SPAN_MARK_MARK); 562 } 563 564 private static void endFont(SpannableStringBuilder text) { 565 int len = text.length(); 566 Object obj = getLast(text, Font.class); 567 int where = text.getSpanStart(obj); 568 569 text.removeSpan(obj); 570 571 if (where != len) { 572 Font f = (Font) obj; 573 574 if (f.mColor != null) { 575 int c = -1; 576 577 if (f.mColor.equalsIgnoreCase("aqua")) { 578 c = 0x00FFFF; 579 } else if (f.mColor.equalsIgnoreCase("black")) { 580 c = 0x000000; 581 } else if (f.mColor.equalsIgnoreCase("blue")) { 582 c = 0x0000FF; 583 } else if (f.mColor.equalsIgnoreCase("fuchsia")) { 584 c = 0xFF00FF; 585 } else if (f.mColor.equalsIgnoreCase("green")) { 586 c = 0x008000; 587 } else if (f.mColor.equalsIgnoreCase("grey")) { 588 c = 0x808080; 589 } else if (f.mColor.equalsIgnoreCase("lime")) { 590 c = 0x00FF00; 591 } else if (f.mColor.equalsIgnoreCase("maroon")) { 592 c = 0x800000; 593 } else if (f.mColor.equalsIgnoreCase("navy")) { 594 c = 0x000080; 595 } else if (f.mColor.equalsIgnoreCase("olive")) { 596 c = 0x808000; 597 } else if (f.mColor.equalsIgnoreCase("purple")) { 598 c = 0x800080; 599 } else if (f.mColor.equalsIgnoreCase("red")) { 600 c = 0xFF0000; 601 } else if (f.mColor.equalsIgnoreCase("silver")) { 602 c = 0xC0C0C0; 603 } else if (f.mColor.equalsIgnoreCase("teal")) { 604 c = 0x008080; 605 } else if (f.mColor.equalsIgnoreCase("white")) { 606 c = 0xFFFFFF; 607 } else if (f.mColor.equalsIgnoreCase("yellow")) { 608 c = 0xFFFF00; 609 } else { 610 try { 611 c = XmlUtils.convertValueToInt(f.mColor, -1); 612 } catch (NumberFormatException nfe) { 613 // Can't understand the color, so just drop it. 614 } 615 } 616 617 if (c != -1) { 618 text.setSpan(new ForegroundColorSpan(c | 0xFF000000), 619 where, len, 620 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 621 } 622 } 623 624 if (f.mFace != null) { 625 text.setSpan(new TypefaceSpan(f.mFace), where, len, 626 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 627 } 628 } 629 } 630 631 private static void startA(SpannableStringBuilder text, Attributes attributes) { 632 String href = attributes.getValue("", "href"); 633 634 int len = text.length(); 635 text.setSpan(new Href(href), len, len, Spannable.SPAN_MARK_MARK); 636 } 637 638 private static void endA(SpannableStringBuilder text) { 639 int len = text.length(); 640 Object obj = getLast(text, Href.class); 641 int where = text.getSpanStart(obj); 642 643 text.removeSpan(obj); 644 645 if (where != len) { 646 Href h = (Href) obj; 647 648 if (h.mHref != null) { 649 text.setSpan(new URLSpan(h.mHref), where, len, 650 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 651 } 652 } 653 } 654 655 private static void endHeader(SpannableStringBuilder text) { 656 int len = text.length(); 657 Object obj = getLast(text, Header.class); 658 659 int where = text.getSpanStart(obj); 660 661 text.removeSpan(obj); 662 663 // Back off not to change only the text, not the blank line. 664 while (len > where && text.charAt(len - 1) == '\n') { 665 len--; 666 } 667 668 if (where != len) { 669 Header h = (Header) obj; 670 671 text.setSpan(new RelativeSizeSpan(HEADER_SIZES[h.mLevel]), 672 where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 673 text.setSpan(new StyleSpan(Typeface.BOLD), 674 where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 675 } 676 } 677 678 public void setDocumentLocator(Locator locator) { 679 } 680 681 public void startDocument() throws SAXException { 682 } 683 684 public void endDocument() throws SAXException { 685 } 686 687 public void startPrefixMapping(String prefix, String uri) throws SAXException { 688 } 689 690 public void endPrefixMapping(String prefix) throws SAXException { 691 } 692 693 public void startElement(String uri, String localName, String qName, Attributes attributes) 694 throws SAXException { 695 handleStartTag(localName, attributes); 696 } 697 698 public void endElement(String uri, String localName, String qName) throws SAXException { 699 handleEndTag(localName); 700 } 701 702 public void characters(char ch[], int start, int length) throws SAXException { 703 mSpannableStringBuilder.append(CharBuffer.wrap(ch, start, length)); 704 } 705 706 public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { 707 } 708 709 public void processingInstruction(String target, String data) throws SAXException { 710 } 711 712 public void skippedEntity(String name) throws SAXException { 713 } 714 715 private static class Bold { } 716 private static class Italic { } 717 private static class Underline { } 718 private static class Big { } 719 private static class Small { } 720 private static class Monospace { } 721 private static class Blockquote { } 722 private static class Super { } 723 private static class Sub { } 724 725 private static class Font { 726 public String mColor; 727 public String mFace; 728 729 public Font(String color, String face) { 730 mColor = color; 731 mFace = face; 732 } 733 } 734 735 private static class Href { 736 public String mHref; 737 738 public Href(String href) { 739 mHref = href; 740 } 741 } 742 743 private static class Header { 744 private int mLevel; 745 746 public Header(int level) { 747 mLevel = level; 748 } 749 } 750} 751