1 /* Copyright 2002-2006, 2009, 2014, 2018 Elliotte Rusty Harold 2 3 This library is free software; you can redistribute it and/or modify 4 it under the terms of version 2.1 of the GNU Lesser General Public 5 License as published by the Free Software Foundation. 6 7 This library is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 GNU Lesser General Public License for more details. 11 12 You should have received a copy of the GNU Lesser General Public 13 License along with this library; if not, write to the 14 Free Software Foundation, Inc., 59 Temple Place, Suite 330, 15 Boston, MA 02111-1307 USA 16 17 You can contact Elliotte Rusty Harold by sending e-mail to 18 elharo@ibiblio.org. Please include the word "XOM" in the 19 subject line. The XOM home page is located at http://www.xom.nu/ 20 */ 21 22 23 package nu.xom; 24 25 import java.util.ArrayList; 26 27 import org.xml.sax.ContentHandler; 28 import org.xml.sax.DTDHandler; 29 import org.xml.sax.Locator; 30 import org.xml.sax.SAXException; 31 import org.xml.sax.ext.DeclHandler; 32 import org.xml.sax.ext.LexicalHandler; 33 34 /** 35 * @author Elliotte Rusty Harold 36 * @version 1.2.11 37 * 38 */ 39 class XOMHandler 40 implements ContentHandler, LexicalHandler, DeclHandler, DTDHandler { 41 42 protected Document document; 43 protected String documentBaseURI; 44 45 // parent is never null. It is the node we're adding children 46 // to. current corresponds to the most recent startElement() 47 // method and may be null if we've skipped it (makeElement 48 // returned null.) If we didn't skip it, then parent and 49 // current should be the same node. 50 protected ParentNode parent; 51 protected ParentNode current; 52 protected ArrayList parents; 53 protected boolean inProlog; 54 protected boolean inDTD; 55 protected int position; // current number of items in prolog 56 protected Locator locator; 57 protected DocType doctype; 58 protected StringBuffer internalDTDSubset; 59 protected NodeFactory factory; 60 boolean usingCrimson = false; 61 62 XOMHandler(NodeFactory factory)63 XOMHandler(NodeFactory factory) { 64 this.factory = factory; 65 } 66 67 setDocumentLocator(Locator locator)68 public void setDocumentLocator(Locator locator) { 69 this.locator = locator; 70 } 71 72 getDocument()73 Document getDocument() { 74 return document; 75 } 76 77 78 // See http://www.servlets.com/archive/servlet/ReadMsg?msgId=554071&listName=jdom-interest 79 // This method is called to avoid leaking document sized memory 80 // when a Builder is not immediately reused freeMemory()81 void freeMemory() { 82 document = null; 83 parent = null; 84 current = null; 85 parents = null; 86 locator = null; 87 doctype = null; 88 internalDTDSubset = null; 89 System.gc(); 90 } 91 92 startDocument()93 public void startDocument() { 94 95 inDTD = false; 96 document = factory.startMakingDocument(); 97 parent = document; 98 current = document; 99 parents = new ArrayList(); 100 parents.add(document); 101 inProlog = true; 102 position = 0; 103 textString = null; 104 doctype = null; 105 if (locator != null) { 106 documentBaseURI = locator.getSystemId(); 107 // According to the XML spec, 108 // "It is an error for a fragment identifier 109 // (beginning with a # character) to be part of a system identifier" 110 // but some parsers including Xerces seem to get this wrong, so we'll 111 document.setBaseURI(documentBaseURI); 112 } 113 buffer = null; 114 115 } 116 117 endDocument()118 public void endDocument() { 119 factory.finishMakingDocument(document); 120 parents.remove(parents.size()-1); 121 } 122 123 startElement(String namespaceURI, String localName, String qualifiedName, org.xml.sax.Attributes attributes)124 public void startElement(String namespaceURI, String localName, 125 String qualifiedName, org.xml.sax.Attributes attributes) throws SAXException { 126 127 flushText(); 128 Element element; 129 if (parent != document) { 130 element = factory.startMakingElement(qualifiedName, namespaceURI); 131 } 132 else { // root 133 element = factory.makeRootElement(qualifiedName, namespaceURI); 134 if (element == null) { // null root; that's a no-no 135 throw new NullPointerException( 136 "Factory failed to create root element." 137 ); 138 } 139 document.setRootElement(element); 140 inProlog = false; 141 } 142 143 current = element; 144 // Need to push this, even if it's null 145 parents.add(element); 146 147 if (element != null) { // wasn't filtered out 148 if (parent != document) { 149 // a.k.a. parent not instanceof Document 150 parent.appendChild(element); 151 } 152 // This is optimized for the very common case where 153 // everything in the document has the same actual base URI. 154 // It may add redundant base URIs in cases like XInclude 155 // where different parts of the document have different 156 // base URIs. 157 if (locator != null) { 158 String baseURI = locator.getSystemId(); 159 if (baseURI != null && !baseURI.equals(documentBaseURI)) { 160 element.setActualBaseURI(baseURI); 161 } 162 } 163 164 // Attach the attributes; this must be done before the 165 // namespaces are attached. 166 // XXX pull out length 167 168 // XXX we've got a pretty good guess at how many attributes there 169 // will be here; we should ensureCapacity up to that length 170 for (int i = 0; i < attributes.getLength(); i++) { 171 String qName = attributes.getQName(i); 172 if (qName.startsWith("xmlns:") || qName.equals("xmlns")) { 173 continue; 174 } 175 else { 176 String namespace = attributes.getURI(i); 177 String value = attributes.getValue(i); 178 Nodes nodes = factory.makeAttribute( 179 qName, 180 namespace, 181 value, 182 convertStringToType(attributes.getType(i)) 183 ); 184 int numberChildren = 0; 185 for (int j=0; j < nodes.size(); j++) { 186 Node node = nodes.get(j); 187 if (node.isAttribute()) { 188 factory.addAttribute(element, (Attribute) node); 189 } 190 else { 191 factory.insertChild(element, node, numberChildren++); 192 } 193 } 194 } 195 } 196 197 // Attach the namespaces 198 for (int i = 0; i < attributes.getLength(); i++) { 199 String qName = attributes.getQName(i); 200 if (qName.startsWith("xmlns:")) { 201 String namespaceName = attributes.getValue(i); 202 String namespacePrefix = qName.substring(6); 203 String currentValue 204 = element.getNamespaceURI(namespacePrefix); 205 if (!namespaceName.equals(currentValue) && ! namespacePrefix.equals(element.getNamespacePrefix())) { 206 element.addNamespaceDeclaration( 207 namespacePrefix, namespaceName); 208 } 209 } 210 else if (qName.equals("xmlns")) { 211 String namespaceName = attributes.getValue(i); 212 String namespacePrefix = ""; 213 String currentValue 214 = element.getNamespaceURI(namespacePrefix); 215 if (!namespaceName.equals(currentValue) && ! "".equals(element.getNamespacePrefix())) { 216 element.addNamespaceDeclaration(namespacePrefix, 217 namespaceName); 218 } 219 } 220 } 221 222 // this is the new parent 223 parent = element; 224 } 225 226 } 227 228 endElement( String namespaceURI, String localName, String qualifiedName)229 public void endElement( 230 String namespaceURI, String localName, String qualifiedName) { 231 232 // If we're immediately inside a skipped element 233 // we need to reset current to null, not to the parent 234 current = (ParentNode) parents.remove(parents.size()-1); 235 flushText(); 236 237 if (current != null) { 238 parent = current.getParent(); 239 Nodes result = factory.finishMakingElement((Element) current); 240 241 // Optimization for default case where result only contains current 242 if (result.size() != 1 || result.get(0) != current) { 243 if (!parent.isDocument()) { 244 // allow factories to detach the element itself in 245 // finishMakingElement 246 int childCount = parent.getChildCount(); 247 try { 248 parent.removeChild(childCount - 1); 249 } 250 catch (IndexOutOfBoundsException ex) { 251 throw new XMLException( 252 "Factory detached element in finishMakingElement()", 253 ex); 254 } 255 for (int i=0; i < result.size(); i++) { 256 Node node = result.get(i); 257 if (node.isAttribute()) { 258 ((Element) parent).addAttribute((Attribute) node); 259 } 260 else { 261 parent.appendChild(node); 262 } 263 } 264 } 265 else { // root element 266 Document doc = (Document) parent; 267 Element currentRoot = doc.getRootElement(); 268 boolean beforeRoot = true; 269 for (int i=0; i < result.size(); i++) { 270 Node node = result.get(i); 271 if (node.isElement()) { 272 if (node != currentRoot) { 273 if (!beforeRoot) { 274 // already set root, oops 275 throw new IllegalAddException("Factory returned multiple roots"); 276 } 277 doc.setRootElement((Element) node); 278 } 279 beforeRoot = false; 280 } 281 else if (beforeRoot) { 282 doc.insertChild(node, doc.indexOf(doc.getRootElement())); 283 } 284 else { 285 doc.appendChild(node); 286 } 287 } 288 if (beforeRoot) { 289 // somebody tried to replace the root element with 290 // no element at all. That's a no-no 291 throw new WellformednessException( 292 "Factory attempted to remove the root element"); 293 } 294 } 295 } 296 } 297 298 } 299 300 convertStringToType(String saxType)301 static Attribute.Type convertStringToType(String saxType) { 302 303 if (saxType.equals("CDATA")) return Attribute.Type.CDATA; 304 if (saxType.equals("ID")) return Attribute.Type.ID; 305 if (saxType.equals("IDREF")) return Attribute.Type.IDREF; 306 if (saxType.equals("IDREFS")) return Attribute.Type.IDREFS; 307 if (saxType.equals("NMTOKEN")) return Attribute.Type.NMTOKEN; 308 if (saxType.equals("NMTOKENS")) return Attribute.Type.NMTOKENS; 309 if (saxType.equals("ENTITY")) return Attribute.Type.ENTITY; 310 if (saxType.equals("ENTITIES")) return Attribute.Type.ENTITIES; 311 if (saxType.equals("NOTATION")) return Attribute.Type.NOTATION; 312 313 // non-standard but some parsers use this 314 if (saxType.equals("ENUMERATION")) { 315 return Attribute.Type.ENUMERATION; 316 } 317 if (saxType.startsWith("(")) return Attribute.Type.ENUMERATION; 318 319 return Attribute.Type.UNDECLARED; 320 321 } 322 323 324 protected String textString = null; 325 protected StringBuffer buffer = null; 326 characters(char[] text, int start, int length)327 public void characters(char[] text, int start, int length) throws SAXException { 328 if (length <= 0) return; 329 if (textString == null) textString = new String(text, start, length); 330 else { 331 if (buffer == null) buffer = new StringBuffer(textString); 332 buffer.append(text, start, length); 333 } 334 if (finishedCDATA) inCDATA = false; 335 336 } 337 338 339 // accumulate all text that's in the buffer into a text node flushText()340 private void flushText() { 341 342 if (buffer != null) { 343 textString = buffer.toString(); 344 buffer = null; 345 } 346 347 if (textString != null) { 348 Nodes result; 349 if (!inCDATA) { 350 result = factory.makeText(textString); 351 } 352 else { 353 result = factory.makeCDATASection(textString); 354 } 355 for (int i=0; i < result.size(); i++) { 356 Node node = result.get(i); 357 if (node.isAttribute()) { 358 ((Element) parent).addAttribute((Attribute) node); 359 } 360 else { 361 parent.appendChild(node); 362 } 363 } 364 textString = null; 365 } 366 inCDATA = false; 367 finishedCDATA = false; 368 369 } 370 371 ignorableWhitespace( char[] text, int start, int length)372 public void ignorableWhitespace( 373 char[] text, int start, int length) throws SAXException { 374 characters(text, start, length); 375 } 376 377 processingInstruction(String target, String data)378 public void processingInstruction(String target, String data) throws SAXException { 379 380 if (!inDTD) flushText(); 381 if (inDTD && !inInternalSubset()) return; 382 Nodes result = factory.makeProcessingInstruction(target, data); 383 384 for (int i = 0; i < result.size(); i++) { 385 Node node = result.get(i); 386 if (!inDTD) { 387 if (inProlog) { 388 parent.insertChild(node, position); 389 position++; 390 } 391 else { 392 if (node.isAttribute()) { 393 ((Element) parent).addAttribute((Attribute) node); 394 } 395 else parent.appendChild(node); 396 } 397 } 398 else { 399 if (node.isProcessingInstruction() || node.isComment()) { 400 internalDTDSubset.append(" "); 401 internalDTDSubset.append(node.toXML()); 402 internalDTDSubset.append("\n"); 403 } 404 else { 405 throw new XMLException("Factory tried to put a " 406 + node.getClass().getName() 407 + " in the internal DTD subset"); 408 } 409 } 410 } 411 412 } 413 414 415 // XOM handles this with attribute values; not prefix mappings startPrefixMapping(String prefix, String uri)416 public void startPrefixMapping(String prefix, String uri) {} endPrefixMapping(String prefix)417 public void endPrefixMapping(String prefix) {} 418 skippedEntity(String name)419 public void skippedEntity(String name) { 420 421 // Xerces 2.7 now calls this method in the DTD 422 // for parameter entities it doesn't resolve. We can ignore these. 423 if (name.startsWith("%")) return; 424 flushText(); 425 throw new XMLException("Could not resolve entity " + name); 426 427 } 428 429 430 // LexicalHandler events startDTD(String rootName, String publicID, String systemID)431 public void startDTD(String rootName, String publicID, 432 String systemID) throws SAXException { 433 434 inDTD = true; 435 Nodes result = factory.makeDocType(rootName, publicID, systemID); 436 for (int i = 0; i < result.size(); i++) { 437 Node node = result.get(i); 438 document.insertChild(node, position); 439 position++; 440 if (node.isDocType()) { 441 DocType doctype = (DocType) node; 442 internalDTDSubset = new StringBuffer(); 443 this.doctype = doctype; 444 } 445 } 446 447 } 448 449 endDTD()450 public void endDTD() { 451 452 inDTD = false; 453 if (doctype != null) { 454 doctype.setInternalDTDSubset(internalDTDSubset.toString()); 455 } 456 457 } 458 459 460 protected boolean inExternalSubset = false; 461 462 // We have a problem here. Xerces gets this right, 463 // but Crimson and possibly other parsers don't properly 464 // report these entities, or perhaps just not tag them 465 // with [dtd] like they're supposed to. startEntity(String name)466 public void startEntity(String name) { 467 if (name.equals("[dtd]")) inExternalSubset = true; 468 } 469 470 endEntity(String name)471 public void endEntity(String name) { 472 if (name.equals("[dtd]")) inExternalSubset = false; 473 } 474 475 476 protected boolean inCDATA = false; 477 protected boolean finishedCDATA = false; 478 479 startCDATA()480 public void startCDATA() { 481 if (textString == null) inCDATA = true; 482 finishedCDATA = false; 483 } 484 485 endCDATA()486 public void endCDATA() { 487 finishedCDATA = true; 488 } 489 490 comment(char[] text, int start, int length)491 public void comment(char[] text, int start, int length) throws SAXException { 492 493 if (!inDTD) flushText(); 494 if (inDTD && !inInternalSubset()) return; 495 496 Nodes result = factory.makeComment(new String(text, start, length)); 497 498 for (int i = 0; i < result.size(); i++) { 499 Node node = result.get(i); 500 if (!inDTD) { 501 if (inProlog) { 502 parent.insertChild(node, position); 503 position++; 504 } 505 else { 506 if (node instanceof Attribute) { 507 ((Element) parent).addAttribute((Attribute) node); 508 } 509 else parent.appendChild(node); 510 } 511 } 512 else { 513 if (node.isComment() || node.isProcessingInstruction()) { 514 internalDTDSubset.append(" "); 515 internalDTDSubset.append(node.toXML()); 516 internalDTDSubset.append("\n"); 517 } 518 else { 519 throw new XMLException("Factory tried to put a " 520 + node.getClass().getName() 521 + " in the internal DTD subset"); 522 } 523 } 524 } 525 526 } 527 528 529 // TODO(elharo) an untrusted parser could push in bad names and 530 // values in declaration in the internal DTD subset. 531 // Possibly declarations should be created in the factory too. elementDecl(String name, String model)532 public void elementDecl(String name, String model) { 533 534 if (inInternalSubset() && doctype != null) { 535 internalDTDSubset.append(" <!ELEMENT "); 536 internalDTDSubset.append(name); 537 internalDTDSubset.append(' '); 538 internalDTDSubset.append(model); 539 // workaround for Crimson bug 540 if (model.indexOf("#PCDATA") > 0 && model.indexOf('|') > 0) { 541 if (model.endsWith(")")) { 542 internalDTDSubset.append('*'); 543 } 544 } 545 internalDTDSubset.append(">\n"); 546 } 547 548 } 549 550 551 // This method only behaves properly when called from the DeclHandler 552 // and DTDHandler callbacks; i.e. from inside the DTD; 553 // It is not intended for use anywhere in the document. inInternalSubset()554 protected boolean inInternalSubset() { 555 556 if (!usingCrimson) { 557 return !inExternalSubset; 558 } 559 String currentURI = locator.getSystemId(); 560 if (currentURI == this.documentBaseURI) return true; 561 if (currentURI == null) return false; 562 if (currentURI.equals(this.documentBaseURI)) return true; 563 return false; 564 565 } 566 567 attributeDecl(String elementName, String attributeName, String type, String mode, String defaultValue)568 public void attributeDecl(String elementName, 569 String attributeName, String type, String mode, 570 String defaultValue) { 571 572 // workaround for Crimson bug 573 if (type.startsWith("NOTATION ")) { 574 if (type.indexOf('(') == -1 && ! type.endsWith(")")) { 575 type = "NOTATION (" + type.substring("NOTATION ".length()) + ")"; 576 } 577 } 578 579 if (inInternalSubset() && doctype != null) { 580 internalDTDSubset.append(" <!ATTLIST "); 581 internalDTDSubset.append(elementName); 582 internalDTDSubset.append(' '); 583 internalDTDSubset.append(attributeName); 584 internalDTDSubset.append(' '); 585 internalDTDSubset.append(type); 586 if (mode != null) { 587 internalDTDSubset.append(' '); 588 internalDTDSubset.append(mode); 589 } 590 if (defaultValue != null) { 591 internalDTDSubset.append(' '); 592 internalDTDSubset.append('"'); 593 internalDTDSubset.append( 594 escapeReservedCharactersInDefaultAttributeValues(defaultValue) 595 ); 596 internalDTDSubset.append('\"'); 597 } 598 internalDTDSubset.append(">\n"); 599 } 600 601 } 602 603 internalEntityDecl(String name, String value)604 public void internalEntityDecl(String name, 605 String value) { 606 607 if (inInternalSubset() && doctype != null) { 608 internalDTDSubset.append(" <!ENTITY "); 609 if (name.startsWith("%")) { 610 internalDTDSubset.append("% "); 611 internalDTDSubset.append(name.substring(1)); 612 } 613 else { 614 internalDTDSubset.append(name); 615 } 616 internalDTDSubset.append(" \""); 617 internalDTDSubset.append(escapeReservedCharactersInDeclarations(value)); 618 internalDTDSubset.append("\">\n"); 619 } 620 621 } 622 623 externalEntityDecl(String name, String publicID, String systemID)624 public void externalEntityDecl(String name, 625 String publicID, String systemID) { 626 627 if (inInternalSubset() && doctype != null) { 628 internalDTDSubset.append(" <!ENTITY "); 629 if (name.startsWith("%")) { 630 internalDTDSubset.append("% "); 631 internalDTDSubset.append(name.substring(1)); 632 } 633 else { 634 internalDTDSubset.append(name); 635 } 636 637 if (locator != null && URIUtil.isAbsolute(systemID)) { 638 String documentURL = locator.getSystemId(); 639 // work around Crimson style file:/root URLs 640 if (documentURL != null) { 641 if (documentURL.startsWith("file:/") && !documentURL.startsWith("file:///")) { 642 documentURL = "file://" + documentURL.substring(5); 643 } 644 if (systemID.startsWith("file:/") && !systemID.startsWith("file:///")) { 645 systemID = "file://" + systemID.substring(5); 646 } 647 systemID = URIUtil.relativize(documentURL, systemID); 648 } 649 } 650 651 if (publicID != null) { 652 internalDTDSubset.append(" PUBLIC \""); 653 internalDTDSubset.append(publicID); 654 internalDTDSubset.append("\" \""); 655 internalDTDSubset.append(systemID); 656 } 657 else { 658 // need to escape system ID???? could it contain an ampersand? 659 internalDTDSubset.append(" SYSTEM \""); 660 internalDTDSubset.append(systemID); 661 } 662 internalDTDSubset.append("\">\n"); 663 664 } 665 666 } 667 668 notationDecl(String name, String publicID, String systemID)669 public void notationDecl(String name, String publicID, 670 String systemID) { 671 672 if (systemID != null) { 673 systemID = escapeReservedCharactersInDeclarations(systemID); 674 } 675 676 if (inInternalSubset() && doctype != null) { 677 internalDTDSubset.append(" <!NOTATION "); 678 internalDTDSubset.append(name); 679 if (publicID != null) { 680 internalDTDSubset.append(" PUBLIC \""); 681 internalDTDSubset.append(publicID); 682 internalDTDSubset.append('"'); 683 if (systemID != null) { 684 internalDTDSubset.append(" \""); 685 internalDTDSubset.append(systemID); 686 internalDTDSubset.append('"'); 687 } 688 } 689 else { 690 internalDTDSubset.append(" SYSTEM \""); 691 internalDTDSubset.append(systemID); 692 internalDTDSubset.append('"'); 693 } 694 internalDTDSubset.append(">\n"); 695 } 696 697 } 698 699 unparsedEntityDecl(String name, String publicID, String systemID, String notationName)700 public void unparsedEntityDecl(String name, String publicID, 701 String systemID, String notationName) { 702 703 // escapable characters???? 704 if (inInternalSubset() && doctype != null) { 705 internalDTDSubset.append(" <!ENTITY "); 706 if (publicID != null) { 707 internalDTDSubset.append(name); 708 internalDTDSubset.append(" PUBLIC \""); 709 internalDTDSubset.append(publicID); 710 internalDTDSubset.append("\" \""); 711 internalDTDSubset.append(systemID); 712 internalDTDSubset.append("\" NDATA "); 713 internalDTDSubset.append(notationName); 714 } 715 else { 716 internalDTDSubset.append(name); 717 internalDTDSubset.append(" SYSTEM \""); 718 internalDTDSubset.append(systemID); 719 internalDTDSubset.append("\" NDATA "); 720 internalDTDSubset.append(notationName); 721 } 722 internalDTDSubset.append(">\n"); 723 } 724 725 } 726 727 escapeReservedCharactersInDeclarations(String s)728 private static String escapeReservedCharactersInDeclarations(String s) { 729 730 int length = s.length(); 731 StringBuffer result = new StringBuffer(length); 732 for (int i = 0; i < length; i++) { 733 char c = s.charAt(i); 734 switch (c) { 735 case '\r': 736 result.append("
"); 737 break; 738 case 14: 739 // placeholder for table lookup 740 break; 741 case 15: 742 // placeholder for table lookup 743 break; 744 case 16 : 745 // placeholder for table lookup 746 break; 747 case 17: 748 // placeholder for table lookup 749 break; 750 case 18: 751 // placeholder for table lookup 752 break; 753 case 19: 754 // placeholder for table lookup 755 break; 756 case 20: 757 // placeholder for table lookup 758 break; 759 case 21: 760 // placeholder for table lookup 761 break; 762 case 22: 763 // placeholder for table lookup 764 break; 765 case 23: 766 // placeholder for table lookup 767 break; 768 case 24: 769 // placeholder for table lookup 770 break; 771 case 25: 772 // placeholder for table lookup 773 break; 774 case 26: 775 // placeholder for table lookup 776 break; 777 case 27: 778 // placeholder for table lookup 779 break; 780 case 28: 781 // placeholder for table lookup 782 break; 783 case 29: 784 // placeholder for table lookup 785 break; 786 case 30: 787 // placeholder for table lookup 788 break; 789 case 31: 790 // placeholder for table lookup 791 break; 792 case ' ': 793 result.append(' '); 794 break; 795 case '!': 796 result.append('!'); 797 break; 798 case '\"': 799 result.append("""); 800 break; 801 case '#': 802 result.append('#'); 803 break; 804 case '$': 805 result.append('$'); 806 break; 807 case '%': 808 result.append("%"); 809 break; 810 case '&': 811 result.append("&"); 812 break; 813 default: 814 result.append(c); 815 } 816 } 817 818 return result.toString(); 819 820 } 821 822 escapeReservedCharactersInDefaultAttributeValues(String s)823 private static String escapeReservedCharactersInDefaultAttributeValues(String s) { 824 825 int length = s.length(); 826 StringBuffer result = new StringBuffer(length); 827 for (int i = 0; i < length; i++) { 828 char c = s.charAt(i); 829 switch (c) { 830 case '\r': 831 result.append("
"); 832 break; 833 case 14: 834 // placeholder for table lookup 835 break; 836 case 15: 837 // placeholder for table lookup 838 break; 839 case 16 : 840 // placeholder for table lookup 841 break; 842 case 17: 843 // placeholder for table lookup 844 break; 845 case 18: 846 // placeholder for table lookup 847 break; 848 case 19: 849 // placeholder for table lookup 850 break; 851 case 20: 852 // placeholder for table lookup 853 break; 854 case 21: 855 // placeholder for table lookup 856 break; 857 case 22: 858 // placeholder for table lookup 859 break; 860 case 23: 861 // placeholder for table lookup 862 break; 863 case 24: 864 // placeholder for table lookup 865 break; 866 case 25: 867 // placeholder for table lookup 868 break; 869 case 26: 870 // placeholder for table lookup 871 break; 872 case 27: 873 // placeholder for table lookup 874 break; 875 case 28: 876 // placeholder for table lookup 877 break; 878 case 29: 879 // placeholder for table lookup 880 break; 881 case 30: 882 // placeholder for table lookup 883 break; 884 case 31: 885 // placeholder for table lookup 886 break; 887 case ' ': 888 result.append(' '); 889 break; 890 case '!': 891 result.append('!'); 892 break; 893 case '\"': 894 result.append("""); 895 break; 896 case '#': 897 result.append('#'); 898 break; 899 case '$': 900 result.append('$'); 901 break; 902 case '%': 903 result.append("%"); 904 break; 905 case '&': 906 result.append("&"); 907 break; 908 case '\'': 909 result.append('\''); 910 break; 911 case '(': 912 result.append('('); 913 break; 914 case ')': 915 result.append(')'); 916 break; 917 case '*': 918 result.append('*'); 919 break; 920 case '+': 921 result.append('+'); 922 break; 923 case ',': 924 result.append(','); 925 break; 926 case '-': 927 result.append('-'); 928 break; 929 case '.': 930 result.append('.'); 931 break; 932 case '/': 933 result.append('/'); 934 break; 935 case '0': 936 result.append('0'); 937 break; 938 case '1': 939 result.append('1'); 940 break; 941 case '2': 942 result.append('2'); 943 break; 944 case '3': 945 result.append('3'); 946 break; 947 case '4': 948 result.append('4'); 949 break; 950 case '5': 951 result.append('5'); 952 break; 953 case '6': 954 result.append('6'); 955 break; 956 case '7': 957 result.append('7'); 958 break; 959 case '8': 960 result.append('8'); 961 break; 962 case '9': 963 result.append('9'); 964 break; 965 case ':': 966 result.append(':'); 967 break; 968 case ';': 969 result.append(';'); 970 break; 971 case '<': 972 result.append("<"); 973 break; 974 default: 975 result.append(c); 976 } 977 } 978 979 return result.toString(); 980 981 } 982 983 984 }