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("&#x0D;");
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("&#x22;");
800                     break;
801                 case '#':
802                     result.append('#');
803                     break;
804                 case '$':
805                     result.append('$');
806                     break;
807                 case '%':
808                     result.append("&#x25;");
809                     break;
810                 case '&':
811                     result.append("&#x26;");
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("&#x0D;");
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("&quot;");
895                     break;
896                 case '#':
897                     result.append('#');
898                     break;
899                 case '$':
900                     result.append('$');
901                     break;
902                 case '%':
903                     result.append("&#x25;");
904                     break;
905                 case '&':
906                     result.append("&amp;");
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("&lt;");
973                     break;
974                 default:
975                     result.append(c);
976             }
977         }
978 
979         return result.toString();
980 
981     }
982 
983 
984 }