1 /* 2 * Copyright 2004 Paulo Soares 3 * 4 * The contents of this file are subject to the Mozilla Public License Version 1.1 5 * (the "License"); you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at http://www.mozilla.org/MPL/ 7 * 8 * Software distributed under the License is distributed on an "AS IS" basis, 9 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 10 * for the specific language governing rights and limitations under the License. 11 * 12 * The Original Code is 'iText, a free JAVA-PDF library'. 13 * 14 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by 15 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie. 16 * All Rights Reserved. 17 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer 18 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved. 19 * 20 * Contributor(s): all the names of the contributors are added in the source code 21 * where applicable. 22 * 23 * Alternatively, the contents of this file may be used under the terms of the 24 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the 25 * provisions of LGPL are applicable instead of those above. If you wish to 26 * allow use of your version of this file only under the terms of the LGPL 27 * License and not to allow others to use your version of this file under 28 * the MPL, indicate your decision by deleting the provisions above and 29 * replace them with the notice and other provisions required by the LGPL. 30 * If you do not delete the provisions above, a recipient may use your version 31 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE. 32 * 33 * This library is free software; you can redistribute it and/or modify it 34 * under the terms of the MPL as stated above or under the terms of the GNU 35 * Library General Public License as published by the Free Software Foundation; 36 * either version 2 of the License, or any later version. 37 * 38 * This library is distributed in the hope that it will be useful, but WITHOUT 39 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 40 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more 41 * details. 42 * 43 * Contributions by: 44 * Lubos Strapko 45 * 46 * If you didn't download this code from the following link, you should check if 47 * you aren't using an obsolete version: 48 * http://www.lowagie.com/iText/ 49 */ 50 51 package com.lowagie.text.html.simpleparser; 52 53 import java.io.File; 54 import java.io.IOException; 55 import java.io.Reader; 56 import java.util.ArrayList; 57 import java.util.HashMap; 58 import java.util.Stack; 59 import java.util.StringTokenizer; 60 61 import com.lowagie.text.html.HtmlTags; 62 import com.lowagie.text.html.Markup; 63 import com.lowagie.text.Chunk; 64 import com.lowagie.text.DocListener; 65 import com.lowagie.text.DocumentException; 66 import com.lowagie.text.Element; 67 import com.lowagie.text.ElementTags; 68 import com.lowagie.text.ExceptionConverter; 69 import com.lowagie.text.HeaderFooter; 70 import com.lowagie.text.Image; 71 import com.lowagie.text.ListItem; 72 import com.lowagie.text.Paragraph; 73 import com.lowagie.text.Phrase; 74 import com.lowagie.text.Rectangle; 75 import com.lowagie.text.TextElementArray; 76 import com.lowagie.text.pdf.PdfPTable; 77 import com.lowagie.text.pdf.draw.LineSeparator; 78 import com.lowagie.text.FontProvider; 79 import com.lowagie.text.xml.simpleparser.SimpleXMLDocHandler; 80 import com.lowagie.text.xml.simpleparser.SimpleXMLParser; 81 82 public class HTMLWorker implements SimpleXMLDocHandler, DocListener { 83 84 protected ArrayList objectList; 85 86 protected DocListener document; 87 88 private Paragraph currentParagraph; 89 90 private ChainedProperties cprops = new ChainedProperties(); 91 92 private Stack stack = new Stack(); 93 94 private boolean pendingTR = false; 95 96 private boolean pendingTD = false; 97 98 private boolean pendingLI = false; 99 100 private StyleSheet style = new StyleSheet(); 101 102 private boolean isPRE = false; 103 104 private Stack tableState = new Stack(); 105 106 private boolean skipText = false; 107 108 private HashMap interfaceProps; 109 110 private FactoryProperties factoryProperties = new FactoryProperties(); 111 112 /** Creates a new instance of HTMLWorker 113 * @param document A class that implements <CODE>DocListener</CODE> 114 * */ HTMLWorker(DocListener document)115 public HTMLWorker(DocListener document) { 116 this.document = document; 117 } 118 setStyleSheet(StyleSheet style)119 public void setStyleSheet(StyleSheet style) { 120 this.style = style; 121 } 122 getStyleSheet()123 public StyleSheet getStyleSheet() { 124 return style; 125 } 126 setInterfaceProps(HashMap interfaceProps)127 public void setInterfaceProps(HashMap interfaceProps) { 128 this.interfaceProps = interfaceProps; 129 FontProvider ff = null; 130 if (interfaceProps != null) 131 ff = (FontProvider) interfaceProps.get("font_factory"); 132 if (ff != null) 133 factoryProperties.setFontImp(ff); 134 } 135 getInterfaceProps()136 public HashMap getInterfaceProps() { 137 return interfaceProps; 138 } 139 parse(Reader reader)140 public void parse(Reader reader) throws IOException { 141 SimpleXMLParser.parse(this, null, reader, true); 142 } 143 parseToList(Reader reader, StyleSheet style)144 public static ArrayList parseToList(Reader reader, StyleSheet style) 145 throws IOException { 146 return parseToList(reader, style, null); 147 } 148 parseToList(Reader reader, StyleSheet style, HashMap interfaceProps)149 public static ArrayList parseToList(Reader reader, StyleSheet style, 150 HashMap interfaceProps) throws IOException { 151 HTMLWorker worker = new HTMLWorker(null); 152 if (style != null) 153 worker.style = style; 154 worker.document = worker; 155 worker.setInterfaceProps(interfaceProps); 156 worker.objectList = new ArrayList(); 157 worker.parse(reader); 158 return worker.objectList; 159 } 160 endDocument()161 public void endDocument() { 162 try { 163 for (int k = 0; k < stack.size(); ++k) 164 document.add((Element) stack.elementAt(k)); 165 if (currentParagraph != null) 166 document.add(currentParagraph); 167 currentParagraph = null; 168 } catch (Exception e) { 169 throw new ExceptionConverter(e); 170 } 171 } 172 startDocument()173 public void startDocument() { 174 HashMap h = new HashMap(); 175 style.applyStyle("body", h); 176 cprops.addToChain("body", h); 177 } 178 startElement(String tag, HashMap h)179 public void startElement(String tag, HashMap h) { 180 if (!tagsSupported.containsKey(tag)) 181 return; 182 try { 183 style.applyStyle(tag, h); 184 String follow = (String) FactoryProperties.followTags.get(tag); 185 if (follow != null) { 186 HashMap prop = new HashMap(); 187 prop.put(follow, null); 188 cprops.addToChain(follow, prop); 189 return; 190 } 191 FactoryProperties.insertStyle(h, cprops); 192 if (tag.equals(HtmlTags.ANCHOR)) { 193 cprops.addToChain(tag, h); 194 if (currentParagraph == null) { 195 currentParagraph = new Paragraph(); 196 } 197 stack.push(currentParagraph); 198 currentParagraph = new Paragraph(); 199 return; 200 } 201 if (tag.equals(HtmlTags.NEWLINE)) { 202 if (currentParagraph == null) { 203 currentParagraph = new Paragraph(); 204 } 205 currentParagraph.add(factoryProperties 206 .createChunk("\n", cprops)); 207 return; 208 } 209 if (tag.equals(HtmlTags.HORIZONTALRULE)) { 210 // Attempting to duplicate the behavior seen on Firefox with 211 // http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_hr_test 212 // where an initial break is only inserted when the preceding element doesn't 213 // end with a break, but a trailing break is always inserted. 214 boolean addLeadingBreak = true; 215 if (currentParagraph == null) { 216 currentParagraph = new Paragraph(); 217 addLeadingBreak = false; 218 } 219 if (addLeadingBreak) { // Not a new paragraph 220 int numChunks = currentParagraph.getChunks().size(); 221 if (numChunks == 0 || 222 ((Chunk)(currentParagraph.getChunks().get(numChunks - 1))).getContent().endsWith("\n")) 223 addLeadingBreak = false; 224 } 225 String align = (String) h.get("align"); 226 int hrAlign = Element.ALIGN_CENTER; 227 if (align != null) { 228 if (align.equalsIgnoreCase("left")) 229 hrAlign = Element.ALIGN_LEFT; 230 if (align.equalsIgnoreCase("right")) 231 hrAlign = Element.ALIGN_RIGHT; 232 } 233 String width = (String) h.get("width"); 234 float hrWidth = 1; 235 if (width != null) { 236 float tmpWidth = Markup.parseLength(width, Markup.DEFAULT_FONT_SIZE); 237 if (tmpWidth > 0) hrWidth = tmpWidth; 238 if (!width.endsWith("%")) 239 hrWidth = 100; // Treat a pixel width as 100% for now. 240 } 241 String size = (String) h.get("size"); 242 float hrSize = 1; 243 if (size != null) { 244 float tmpSize = Markup.parseLength(size, Markup.DEFAULT_FONT_SIZE); 245 if (tmpSize > 0) 246 hrSize = tmpSize; 247 } 248 if (addLeadingBreak) 249 currentParagraph.add(Chunk.NEWLINE); 250 currentParagraph.add(new LineSeparator(hrSize, hrWidth, null, hrAlign, currentParagraph.getLeading()/2)); 251 currentParagraph.add(Chunk.NEWLINE); 252 return; 253 } 254 if (tag.equals(HtmlTags.CHUNK) || tag.equals(HtmlTags.SPAN)) { 255 cprops.addToChain(tag, h); 256 return; 257 } 258 if (tag.equals(HtmlTags.IMAGE)) { 259 String src = (String) h.get(ElementTags.SRC); 260 if (src == null) 261 return; 262 cprops.addToChain(tag, h); 263 Image img = null; 264 if (interfaceProps != null) { 265 ImageProvider ip = (ImageProvider) interfaceProps 266 .get("img_provider"); 267 if (ip != null) 268 img = ip.getImage(src, h, cprops, document); 269 if (img == null) { 270 HashMap images = (HashMap) interfaceProps 271 .get("img_static"); 272 if (images != null) { 273 Image tim = (Image) images.get(src); 274 if (tim != null) 275 img = Image.getInstance(tim); 276 } else { 277 if (!src.startsWith("http")) { // relative src references only 278 String baseurl = (String) interfaceProps 279 .get("img_baseurl"); 280 if (baseurl != null) { 281 src = baseurl + src; 282 img = Image.getInstance(src); 283 } 284 } 285 } 286 } 287 } 288 if (img == null) { 289 if (!src.startsWith("http")) { 290 String path = cprops.getProperty("image_path"); 291 if (path == null) 292 path = ""; 293 src = new File(path, src).getPath(); 294 } 295 img = Image.getInstance(src); 296 } 297 String align = (String) h.get("align"); 298 String width = (String) h.get("width"); 299 String height = (String) h.get("height"); 300 String before = cprops.getProperty("before"); 301 String after = cprops.getProperty("after"); 302 if (before != null) 303 img.setSpacingBefore(Float.parseFloat(before)); 304 if (after != null) 305 img.setSpacingAfter(Float.parseFloat(after)); 306 float actualFontSize = Markup.parseLength(cprops 307 .getProperty(ElementTags.SIZE), 308 Markup.DEFAULT_FONT_SIZE); 309 if (actualFontSize <= 0f) 310 actualFontSize = Markup.DEFAULT_FONT_SIZE; 311 float widthInPoints = Markup.parseLength(width, actualFontSize); 312 float heightInPoints = Markup.parseLength(height, 313 actualFontSize); 314 if (widthInPoints > 0 && heightInPoints > 0) { 315 img.scaleAbsolute(widthInPoints, heightInPoints); 316 } else if (widthInPoints > 0) { 317 heightInPoints = img.getHeight() * widthInPoints 318 / img.getWidth(); 319 img.scaleAbsolute(widthInPoints, heightInPoints); 320 } else if (heightInPoints > 0) { 321 widthInPoints = img.getWidth() * heightInPoints 322 / img.getHeight(); 323 img.scaleAbsolute(widthInPoints, heightInPoints); 324 } 325 img.setWidthPercentage(0); 326 if (align != null) { 327 endElement("p"); 328 int ralign = Image.MIDDLE; 329 if (align.equalsIgnoreCase("left")) 330 ralign = Image.LEFT; 331 else if (align.equalsIgnoreCase("right")) 332 ralign = Image.RIGHT; 333 img.setAlignment(ralign); 334 Img i = null; 335 boolean skip = false; 336 if (interfaceProps != null) { 337 i = (Img) interfaceProps.get("img_interface"); 338 if (i != null) 339 skip = i.process(img, h, cprops, document); 340 } 341 if (!skip) 342 document.add(img); 343 cprops.removeChain(tag); 344 } else { 345 cprops.removeChain(tag); 346 if (currentParagraph == null) { 347 currentParagraph = FactoryProperties 348 .createParagraph(cprops); 349 } 350 currentParagraph.add(new Chunk(img, 0, 0)); 351 } 352 return; 353 } 354 endElement("p"); 355 if (tag.equals("h1") || tag.equals("h2") || tag.equals("h3") 356 || tag.equals("h4") || tag.equals("h5") || tag.equals("h6")) { 357 if (!h.containsKey(ElementTags.SIZE)) { 358 int v = 7 - Integer.parseInt(tag.substring(1)); 359 h.put(ElementTags.SIZE, Integer.toString(v)); 360 } 361 cprops.addToChain(tag, h); 362 return; 363 } 364 if (tag.equals(HtmlTags.UNORDEREDLIST)) { 365 if (pendingLI) 366 endElement(HtmlTags.LISTITEM); 367 skipText = true; 368 cprops.addToChain(tag, h); 369 com.lowagie.text.List list = new com.lowagie.text.List(false); 370 try{ 371 list.setIndentationLeft(new Float(cprops.getProperty("indent")).floatValue()); 372 }catch (Exception e) { 373 list.setAutoindent(true); 374 } 375 list.setListSymbol("\u2022"); 376 stack.push(list); 377 return; 378 } 379 if (tag.equals(HtmlTags.ORDEREDLIST)) { 380 if (pendingLI) 381 endElement(HtmlTags.LISTITEM); 382 skipText = true; 383 cprops.addToChain(tag, h); 384 com.lowagie.text.List list = new com.lowagie.text.List(true); 385 try{ 386 list.setIndentationLeft(new Float(cprops.getProperty("indent")).floatValue()); 387 }catch (Exception e) { 388 list.setAutoindent(true); 389 } 390 stack.push(list); 391 return; 392 } 393 if (tag.equals(HtmlTags.LISTITEM)) { 394 if (pendingLI) 395 endElement(HtmlTags.LISTITEM); 396 skipText = false; 397 pendingLI = true; 398 cprops.addToChain(tag, h); 399 ListItem item = FactoryProperties.createListItem(cprops); 400 stack.push(item); 401 return; 402 } 403 if (tag.equals(HtmlTags.DIV) || tag.equals(HtmlTags.BODY) || tag.equals("p")) { 404 cprops.addToChain(tag, h); 405 return; 406 } 407 if (tag.equals(HtmlTags.PRE)) { 408 if (!h.containsKey(ElementTags.FACE)) { 409 h.put(ElementTags.FACE, "Courier"); 410 } 411 cprops.addToChain(tag, h); 412 isPRE = true; 413 return; 414 } 415 if (tag.equals("tr")) { 416 if (pendingTR) 417 endElement("tr"); 418 skipText = true; 419 pendingTR = true; 420 cprops.addToChain("tr", h); 421 return; 422 } 423 if (tag.equals("td") || tag.equals("th")) { 424 if (pendingTD) 425 endElement(tag); 426 skipText = false; 427 pendingTD = true; 428 cprops.addToChain("td", h); 429 stack.push(new IncCell(tag, cprops)); 430 return; 431 } 432 if (tag.equals("table")) { 433 cprops.addToChain("table", h); 434 IncTable table = new IncTable(h); 435 stack.push(table); 436 tableState.push(new boolean[] { pendingTR, pendingTD }); 437 pendingTR = pendingTD = false; 438 skipText = true; 439 return; 440 } 441 } catch (Exception e) { 442 throw new ExceptionConverter(e); 443 } 444 } 445 endElement(String tag)446 public void endElement(String tag) { 447 if (!tagsSupported.containsKey(tag)) 448 return; 449 try { 450 String follow = (String) FactoryProperties.followTags.get(tag); 451 if (follow != null) { 452 cprops.removeChain(follow); 453 return; 454 } 455 if (tag.equals("font") || tag.equals("span")) { 456 cprops.removeChain(tag); 457 return; 458 } 459 if (tag.equals("a")) { 460 if (currentParagraph == null) { 461 currentParagraph = new Paragraph(); 462 } 463 boolean skip = false; 464 if (interfaceProps != null) { 465 ALink i = (ALink) interfaceProps.get("alink_interface"); 466 if (i != null) 467 skip = i.process(currentParagraph, cprops); 468 } 469 if (!skip) { 470 String href = cprops.getProperty("href"); 471 if (href != null) { 472 ArrayList chunks = currentParagraph.getChunks(); 473 int size = chunks.size(); 474 for (int k = 0; k < size; ++k) { 475 Chunk ck = (Chunk) chunks.get(k); 476 ck.setAnchor(href); 477 } 478 } 479 } 480 Paragraph tmp = (Paragraph) stack.pop(); 481 Phrase tmp2 = new Phrase(); 482 tmp2.add(currentParagraph); 483 tmp.add(tmp2); 484 currentParagraph = tmp; 485 cprops.removeChain("a"); 486 return; 487 } 488 if (tag.equals("br")) { 489 return; 490 } 491 if (currentParagraph != null) { 492 if (stack.empty()) 493 document.add(currentParagraph); 494 else { 495 Object obj = stack.pop(); 496 if (obj instanceof TextElementArray) { 497 TextElementArray current = (TextElementArray) obj; 498 current.add(currentParagraph); 499 } 500 stack.push(obj); 501 } 502 } 503 currentParagraph = null; 504 if (tag.equals(HtmlTags.UNORDEREDLIST) 505 || tag.equals(HtmlTags.ORDEREDLIST)) { 506 if (pendingLI) 507 endElement(HtmlTags.LISTITEM); 508 skipText = false; 509 cprops.removeChain(tag); 510 if (stack.empty()) 511 return; 512 Object obj = stack.pop(); 513 if (!(obj instanceof com.lowagie.text.List)) { 514 stack.push(obj); 515 return; 516 } 517 if (stack.empty()) 518 document.add((Element) obj); 519 else 520 ((TextElementArray) stack.peek()).add(obj); 521 return; 522 } 523 if (tag.equals(HtmlTags.LISTITEM)) { 524 pendingLI = false; 525 skipText = true; 526 cprops.removeChain(tag); 527 if (stack.empty()) 528 return; 529 Object obj = stack.pop(); 530 if (!(obj instanceof ListItem)) { 531 stack.push(obj); 532 return; 533 } 534 if (stack.empty()) { 535 document.add((Element) obj); 536 return; 537 } 538 Object list = stack.pop(); 539 if (!(list instanceof com.lowagie.text.List)) { 540 stack.push(list); 541 return; 542 } 543 ListItem item = (ListItem) obj; 544 ((com.lowagie.text.List) list).add(item); 545 ArrayList cks = item.getChunks(); 546 if (!cks.isEmpty()) 547 item.getListSymbol() 548 .setFont(((Chunk) cks.get(0)).getFont()); 549 stack.push(list); 550 return; 551 } 552 if (tag.equals("div") || tag.equals("body")) { 553 cprops.removeChain(tag); 554 return; 555 } 556 if (tag.equals(HtmlTags.PRE)) { 557 cprops.removeChain(tag); 558 isPRE = false; 559 return; 560 } 561 if (tag.equals("p")) { 562 cprops.removeChain(tag); 563 return; 564 } 565 if (tag.equals("h1") || tag.equals("h2") || tag.equals("h3") 566 || tag.equals("h4") || tag.equals("h5") || tag.equals("h6")) { 567 cprops.removeChain(tag); 568 return; 569 } 570 if (tag.equals("table")) { 571 if (pendingTR) 572 endElement("tr"); 573 cprops.removeChain("table"); 574 IncTable table = (IncTable) stack.pop(); 575 PdfPTable tb = table.buildTable(); 576 tb.setSplitRows(true); 577 if (stack.empty()) 578 document.add(tb); 579 else 580 ((TextElementArray) stack.peek()).add(tb); 581 boolean state[] = (boolean[]) tableState.pop(); 582 pendingTR = state[0]; 583 pendingTD = state[1]; 584 skipText = false; 585 return; 586 } 587 if (tag.equals("tr")) { 588 if (pendingTD) 589 endElement("td"); 590 pendingTR = false; 591 cprops.removeChain("tr"); 592 ArrayList cells = new ArrayList(); 593 IncTable table = null; 594 while (true) { 595 Object obj = stack.pop(); 596 if (obj instanceof IncCell) { 597 cells.add(((IncCell) obj).getCell()); 598 } 599 if (obj instanceof IncTable) { 600 table = (IncTable) obj; 601 break; 602 } 603 } 604 table.addCols(cells); 605 table.endRow(); 606 stack.push(table); 607 skipText = true; 608 return; 609 } 610 if (tag.equals("td") || tag.equals("th")) { 611 pendingTD = false; 612 cprops.removeChain("td"); 613 skipText = true; 614 return; 615 } 616 } catch (Exception e) { 617 throw new ExceptionConverter(e); 618 } 619 } 620 text(String str)621 public void text(String str) { 622 if (skipText) 623 return; 624 String content = str; 625 if (isPRE) { 626 if (currentParagraph == null) { 627 currentParagraph = FactoryProperties.createParagraph(cprops); 628 } 629 Chunk chunk = factoryProperties.createChunk(content, cprops); 630 currentParagraph.add(chunk); 631 return; 632 } 633 if (content.trim().length() == 0 && content.indexOf(' ') < 0) { 634 return; 635 } 636 637 StringBuffer buf = new StringBuffer(); 638 int len = content.length(); 639 char character; 640 boolean newline = false; 641 for (int i = 0; i < len; i++) { 642 switch (character = content.charAt(i)) { 643 case ' ': 644 if (!newline) { 645 buf.append(character); 646 } 647 break; 648 case '\n': 649 if (i > 0) { 650 newline = true; 651 buf.append(' '); 652 } 653 break; 654 case '\r': 655 break; 656 case '\t': 657 break; 658 default: 659 newline = false; 660 buf.append(character); 661 } 662 } 663 if (currentParagraph == null) { 664 currentParagraph = FactoryProperties.createParagraph(cprops); 665 } 666 Chunk chunk = factoryProperties.createChunk(buf.toString(), cprops); 667 currentParagraph.add(chunk); 668 } 669 add(Element element)670 public boolean add(Element element) throws DocumentException { 671 objectList.add(element); 672 return true; 673 } 674 clearTextWrap()675 public void clearTextWrap() throws DocumentException { 676 } 677 close()678 public void close() { 679 } 680 newPage()681 public boolean newPage() { 682 return true; 683 } 684 open()685 public void open() { 686 } 687 resetFooter()688 public void resetFooter() { 689 } 690 resetHeader()691 public void resetHeader() { 692 } 693 resetPageCount()694 public void resetPageCount() { 695 } 696 setFooter(HeaderFooter footer)697 public void setFooter(HeaderFooter footer) { 698 } 699 setHeader(HeaderFooter header)700 public void setHeader(HeaderFooter header) { 701 } 702 setMarginMirroring(boolean marginMirroring)703 public boolean setMarginMirroring(boolean marginMirroring) { 704 return false; 705 } 706 707 /** 708 * @see com.lowagie.text.DocListener#setMarginMirroring(boolean) 709 * @since 2.1.6 710 */ setMarginMirroringTopBottom(boolean marginMirroring)711 public boolean setMarginMirroringTopBottom(boolean marginMirroring) { 712 return false; 713 } 714 setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom)715 public boolean setMargins(float marginLeft, float marginRight, 716 float marginTop, float marginBottom) { 717 return true; 718 } 719 setPageCount(int pageN)720 public void setPageCount(int pageN) { 721 } 722 setPageSize(Rectangle pageSize)723 public boolean setPageSize(Rectangle pageSize) { 724 return true; 725 } 726 727 public static final String tagsSupportedString = "ol ul li a pre font span br p div body table td th tr i b u sub sup em strong s strike" 728 + " h1 h2 h3 h4 h5 h6 img hr"; 729 730 public static final HashMap tagsSupported = new HashMap(); 731 732 static { 733 StringTokenizer tok = new StringTokenizer(tagsSupportedString); 734 while (tok.hasMoreTokens()) tok.nextToken()735 tagsSupported.put(tok.nextToken(), null); 736 } 737 738 } 739