1 /* StreamSerializer.java --
2    Copyright (C) 2004,2006 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 package gnu.xml.transform;
39 
40 import gnu.java.lang.CPStringBuilder;
41 
42 import java.io.ByteArrayOutputStream;
43 import java.io.IOException;
44 import java.io.OutputStream;
45 import java.nio.ByteBuffer;
46 import java.nio.CharBuffer;
47 import java.nio.charset.Charset;
48 import java.nio.charset.CharsetEncoder;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.Iterator;
54 import java.util.LinkedList;
55 import java.util.Map;
56 import javax.xml.XMLConstants;
57 import org.w3c.dom.Attr;
58 import org.w3c.dom.Document;
59 import org.w3c.dom.DocumentType;
60 import org.w3c.dom.NamedNodeMap;
61 import org.w3c.dom.Node;
62 
63 /**
64  * Serializes a DOM node to an output stream.
65  *
66  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
67  */
68 public class StreamSerializer
69 {
70 
71   static final int SPACE = 0x20;
72   static final int BANG = 0x21; // !
73   static final int APOS = 0x27; // '
74   static final int SLASH = 0x2f; // /
75   static final int BRA = 0x3c; // <
76   static final int KET = 0x3e; // >
77   static final int EQ = 0x3d; // =
78 
79   /**
80    * HTML 4.01 boolean attributes
81    */
82   static final Map HTML_BOOLEAN_ATTRIBUTES = new HashMap();
83   static
84   {
85     HashSet set;
86 
87     set = new HashSet();
88     set.add("nohref");
89     HTML_BOOLEAN_ATTRIBUTES.put("area", set);
90 
91     set = new HashSet();
92     set.add("ismap");
93     HTML_BOOLEAN_ATTRIBUTES.put("img", set);
94 
95     set = new HashSet();
96     set.add("declare");
97     HTML_BOOLEAN_ATTRIBUTES.put("object", set);
98 
99     set = new HashSet();
100     set.add("noshade");
101     HTML_BOOLEAN_ATTRIBUTES.put("hr", set);
102 
103     set = new HashSet();
104     set.add("compact");
105     HTML_BOOLEAN_ATTRIBUTES.put("dl", set);
106     HTML_BOOLEAN_ATTRIBUTES.put("ol", set);
107     HTML_BOOLEAN_ATTRIBUTES.put("ul", set);
108     HTML_BOOLEAN_ATTRIBUTES.put("dir", set);
109     HTML_BOOLEAN_ATTRIBUTES.put("menu", set);
110 
111     set = new HashSet();
112     set.add("checked");
113     set.add("disabled");
114     set.add("readonly");
115     set.add("ismap");
116     HTML_BOOLEAN_ATTRIBUTES.put("input", set);
117 
118     set = new HashSet();
119     set.add("multiple");
120     set.add("disabled");
121     HTML_BOOLEAN_ATTRIBUTES.put("select", set);
122 
123     set = new HashSet();
124     set.add("disabled");
125     HTML_BOOLEAN_ATTRIBUTES.put("optgroup", set);
126 
127     set = new HashSet();
128     set.add("selected");
129     set.add("disabled");
130     HTML_BOOLEAN_ATTRIBUTES.put("option", set);
131 
132     set = new HashSet();
133     set.add("disabled");
134     set.add("readonly");
135     HTML_BOOLEAN_ATTRIBUTES.put("textarea", set);
136 
137     set = new HashSet();
138     set.add("disabled");
139     HTML_BOOLEAN_ATTRIBUTES.put("button", set);
140 
141     set = new HashSet();
142     set.add("nowrap");
143     HTML_BOOLEAN_ATTRIBUTES.put("th", set);
144     HTML_BOOLEAN_ATTRIBUTES.put("td", set);
145 
146     set = new HashSet();
147     set.add("noresize");
148     HTML_BOOLEAN_ATTRIBUTES.put("frame", set);
149 
150     set = new HashSet();
151     set.add("defer");
152     HTML_BOOLEAN_ATTRIBUTES.put("script", set);
153   }
154 
155   // HTML namespace URIs
156   static final HashSet HTML_URIS = new HashSet();
157   static {
158     HTML_URIS.add("http://www.w3.org/1999/xhtml");
159   }
160 
161   protected final String encoding;
162   final Charset charset;
163   final CharsetEncoder encoder;
164   final int mode;
165   final LinkedList namespaces;
166   protected String eol;
167   Collection cdataSectionElements = Collections.EMPTY_SET;
168 
169   protected boolean discardDefaultContent;
170   protected boolean xmlDeclaration = true;
171 
172   // has a META element with the encoding been added?
173   private boolean htmlEncoded;
174 
StreamSerializer()175   public StreamSerializer()
176   {
177     this(Stylesheet.OUTPUT_XML, null, null);
178   }
179 
StreamSerializer(String encoding)180   public StreamSerializer(String encoding)
181   {
182     this(Stylesheet.OUTPUT_XML, encoding, null);
183   }
184 
StreamSerializer(int mode, String encoding, String eol)185   public StreamSerializer(int mode, String encoding, String eol)
186   {
187     this.mode = mode;
188     if (encoding == null)
189       encoding = (mode == Stylesheet.OUTPUT_HTML) ? "ISO-8859-1" : "UTF-8";
190     this.encoding = encoding.intern();
191     charset = Charset.forName(this.encoding);
192     encoder = charset.newEncoder();
193     this.eol = (eol != null) ? eol : System.getProperty("line.separator");
194     namespaces = new LinkedList();
195   }
196 
setCdataSectionElements(Collection c)197   void setCdataSectionElements(Collection c)
198   {
199     cdataSectionElements = c;
200   }
201 
serialize(final Node node, final OutputStream out)202   public void serialize(final Node node, final OutputStream out)
203     throws IOException
204   {
205     serialize(node, out, false);
206   }
207 
serialize(Node node, final OutputStream out, boolean convertToCdata)208   void serialize(Node node, final OutputStream out,
209                  boolean convertToCdata)
210     throws IOException
211   {
212     while (node != null)
213       {
214         Node next = node.getNextSibling();
215         doSerialize(node, out, convertToCdata);
216         node = next;
217       }
218   }
219 
doSerialize(final Node node, final OutputStream out, boolean convertToCdata)220   private void doSerialize(final Node node, final OutputStream out,
221                            boolean convertToCdata)
222     throws IOException
223   {
224     if (out == null)
225       throw new NullPointerException("no output stream");
226     htmlEncoded = false;
227     String value, prefix;
228     Node children;
229     String uri = node.getNamespaceURI();
230     short nt = node.getNodeType();
231     if (convertToCdata && nt == Node.TEXT_NODE)
232       nt = Node.CDATA_SECTION_NODE;
233     switch (nt)
234       {
235       case Node.ATTRIBUTE_NODE:
236         prefix = node.getPrefix();
237         if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri) ||
238             XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) ||
239             (prefix != null && prefix.startsWith("xmlns:")))
240           {
241             String nsuri = node.getNodeValue();
242             if (isDefined(nsuri, prefix))
243               break;
244             String name = node.getLocalName();
245             if (name == null)
246               {
247                 // Namespace-unaware
248                 name = node.getNodeName();
249                 int ci = name.indexOf(':');
250                 if (ci != -1)
251                   name = name.substring(ci + 1);
252               }
253             define(nsuri, name);
254           }
255         else if (uri != null && !isDefined(uri, prefix))
256           {
257             prefix = define(uri, prefix);
258             String nsname = (prefix == null) ? "xmlns" : "xmlns:" + prefix;
259             out.write(SPACE);
260             out.write(encodeText(nsname));
261             out.write(EQ);
262             String nsvalue = "\"" + encode(uri, true, true) + "\"";
263             out.write(nsvalue.getBytes(encoding));
264           }
265         out.write(SPACE);
266         String a_nodeName = node.getNodeName();
267         out.write(encodeText(a_nodeName));
268         String a_nodeValue = node.getNodeValue();
269         if (mode == Stylesheet.OUTPUT_HTML &&
270             a_nodeName.equals(a_nodeValue) &&
271             isHTMLBoolean((Attr) node, a_nodeName))
272           break;
273         out.write(EQ);
274         value = "\"" + encode(a_nodeValue, true, true) + "\"";
275         out.write(encodeText(value));
276         break;
277       case Node.ELEMENT_NODE:
278         pushNamespaceContext();
279         value = node.getNodeName();
280         out.write(BRA);
281         out.write(encodeText(value));
282         prefix = node.getPrefix();
283         if (uri != null && !isDefined(uri, prefix))
284           {
285             prefix = define(uri, prefix);
286             String nsname = (prefix == null) ? "xmlns" : "xmlns:" + prefix;
287             out.write(SPACE);
288             out.write(encodeText(nsname));
289             out.write(EQ);
290             String nsvalue = "\"" + encode(uri, true, true) + "\"";
291             out.write(encodeText(nsvalue));
292           }
293         NamedNodeMap attrs = node.getAttributes();
294         if (attrs != null)
295           {
296             int len = attrs.getLength();
297             for (int i = 0; i < len; i++)
298               {
299                 Attr attr = (Attr) attrs.item(i);
300                 if (discardDefaultContent && !attr.getSpecified())
301                   {
302                     // NOOP
303                   }
304                 else
305                   serialize(attr, out, false);
306               }
307           }
308         convertToCdata = cdataSectionElements.contains(value);
309         children = node.getFirstChild();
310         if (children == null)
311           {
312             out.write(SLASH);
313             out.write(KET);
314           }
315         else
316           {
317             out.write(KET);
318             serialize(children, out, convertToCdata);
319             out.write(BRA);
320             out.write(SLASH);
321             out.write(encodeText(value));
322             out.write(KET);
323           }
324         popNamespaceContext();
325         break;
326       case Node.TEXT_NODE:
327         value = node.getNodeValue();
328         if (!"yes".equals(node.getUserData("disable-output-escaping")) &&
329             mode != Stylesheet.OUTPUT_TEXT)
330           value = encode(value, false, false);
331         out.write(encodeText(value));
332         break;
333       case Node.CDATA_SECTION_NODE:
334         value = node.getNodeValue();
335         // Where any instanceof of ]]> occur, split into multiple CDATA
336         // sections
337         int bbk = value.indexOf("]]>");
338         while (bbk != -1)
339           {
340             String head = value.substring(0, bbk + 2);
341             out.write(encodeText("<![CDATA[" + head + "]]>"));
342             value = value.substring(bbk + 2);
343             bbk = value.indexOf("]]>");
344           }
345         // Write final tail value
346         out.write(encodeText("<![CDATA[" + value + "]]>"));
347         break;
348       case Node.COMMENT_NODE:
349         value = "<!--" + node.getNodeValue() + "-->";
350         out.write(encodeText(value));
351         Node cp = node.getParentNode();
352         if (cp != null && cp.getNodeType() == Node.DOCUMENT_NODE)
353           out.write(encodeText(eol));
354         break;
355       case Node.DOCUMENT_NODE:
356       case Node.DOCUMENT_FRAGMENT_NODE:
357         if (mode == Stylesheet.OUTPUT_XML)
358           {
359             if ("UTF-16".equalsIgnoreCase(encoding))
360               {
361                 out.write(0xfe);
362                 out.write(0xff);
363               }
364             if (!"yes".equals(node.getUserData("omit-xml-declaration")) &&
365                 xmlDeclaration)
366               {
367                 Document doc = (node instanceof Document) ?
368                   (Document) node : null;
369                 String version = (doc != null) ? doc.getXmlVersion() : null;
370                 if (version == null)
371                   version = (String) node.getUserData("version");
372                 if (version == null)
373                   version = "1.0";
374                 out.write(BRA);
375                 out.write(0x3f);
376                 out.write("xml version=\"".getBytes("US-ASCII"));
377                 out.write(version.getBytes("US-ASCII"));
378                 out.write(0x22);
379                 if (!("UTF-8".equalsIgnoreCase(encoding)))
380                   {
381                     out.write(" encoding=\"".getBytes("US-ASCII"));
382                     out.write(encoding.getBytes("US-ASCII"));
383                     out.write(0x22);
384                   }
385                 if ((doc != null && doc.getXmlStandalone()) ||
386                     "yes".equals(node.getUserData("standalone")))
387                   out.write(" standalone=\"yes\"".getBytes("US-ASCII"));
388                 out.write(0x3f);
389                 out.write(KET);
390                 out.write(encodeText(eol));
391               }
392             // TODO warn if not outputting the declaration would be a
393             // problem
394           }
395         else if (mode == Stylesheet.OUTPUT_HTML)
396           {
397             // Ensure that encoding is accessible if head element is present
398             String mediaType = (String) node.getUserData("media-type");
399             if (mediaType == null)
400               mediaType = "text/html";
401             String contentType = mediaType + "; charset=" +
402               ((encoding.indexOf(' ') != -1) ?
403                 "\"" + encoding + "\"" :
404                 encoding);
405             Document doc = (node instanceof Document) ? (Document) node :
406               node.getOwnerDocument();
407             Node html = null;
408             for (Node ctx = node.getFirstChild(); ctx != null;
409                  ctx = ctx.getNextSibling())
410               {
411                 if (ctx.getNodeType() == Node.ELEMENT_NODE &&
412                     isHTMLElement(ctx, "html"))
413                   {
414                     html = ctx;
415                     break;
416                   }
417               }
418             if (html != null)
419               {
420                 Node head = null;
421                 for (Node ctx = html.getFirstChild(); ctx != null;
422                      ctx = ctx.getNextSibling())
423                   {
424                     if (isHTMLElement(ctx, "head"))
425                       {
426                         head = ctx;
427                         break;
428                       }
429                   }
430                 if (head != null)
431                   {
432                     Node meta = null;
433                     Node metaContent = null;
434                     for (Node ctx = head.getFirstChild(); ctx != null;
435                          ctx = ctx.getNextSibling())
436                       {
437                         if (isHTMLElement(ctx, "meta"))
438                           {
439                             NamedNodeMap metaAttrs = ctx.getAttributes();
440                             int len = metaAttrs.getLength();
441                             String httpEquiv = null;
442                             Node content = null;
443                             for (int i = 0; i < len; i++)
444                               {
445                                 Node attr = metaAttrs.item(i);
446                                 String attrName = attr.getNodeName();
447                                 if ("http-equiv".equalsIgnoreCase(attrName))
448                                   httpEquiv = attr.getNodeValue();
449                                 else if ("content".equalsIgnoreCase(attrName))
450                                   content = attr;
451                               }
452                             if ("Content-Type".equalsIgnoreCase(httpEquiv))
453                               {
454                                 meta = ctx;
455                                 metaContent = content;
456                                 break;
457                               }
458                           }
459                       }
460                     if (meta == null)
461                       {
462                         meta = doc.createElement("meta");
463                         // Insert first
464                         Node first = head.getFirstChild();
465                         if (first == null)
466                           head.appendChild(meta);
467                         else
468                           head.insertBefore(meta, first);
469                         Node metaHttpEquiv = doc.createAttribute("http-equiv");
470                         meta.getAttributes().setNamedItem(metaHttpEquiv);
471                         metaHttpEquiv.setNodeValue("Content-Type");
472                       }
473                     if (metaContent == null)
474                       {
475                         metaContent = doc.createAttribute("content");
476                         meta.getAttributes().setNamedItem(metaContent);
477                       }
478                     metaContent.setNodeValue(contentType);
479                     htmlEncoded = true;
480                   }
481               }
482           }
483         children = node.getFirstChild();
484         if (children != null)
485           serialize(children, out, convertToCdata);
486         break;
487       case Node.DOCUMENT_TYPE_NODE:
488         DocumentType doctype = (DocumentType) node;
489         out.write(BRA);
490         out.write(BANG);
491         out.write(encodeText("DOCTYPE "));
492         value = doctype.getNodeName();
493         out.write(encodeText(value));
494         String publicId = doctype.getPublicId();
495         if (publicId != null)
496           {
497             out.write(encodeText(" PUBLIC "));
498             out.write(APOS);
499             out.write(encodeText(publicId));
500             out.write(APOS);
501           }
502         String systemId = doctype.getSystemId();
503         if (systemId != null)
504           {
505             out.write(encodeText(" SYSTEM "));
506             out.write(APOS);
507             out.write(encodeText(systemId));
508             out.write(APOS);
509           }
510         String internalSubset = doctype.getInternalSubset();
511         if (internalSubset != null)
512           {
513             out.write(encodeText(internalSubset));
514           }
515         out.write(KET);
516         out.write(eol.getBytes(encoding));
517         break;
518       case Node.ENTITY_REFERENCE_NODE:
519         value = "&" + node.getNodeValue() + ";";
520         out.write(encodeText(value));
521         break;
522       case Node.PROCESSING_INSTRUCTION_NODE:
523         value = "<?" + node.getNodeName() + " " + node.getNodeValue() + "?>";
524         out.write(encodeText(value));
525         Node pp = node.getParentNode();
526         if (pp != null && pp.getNodeType() == Node.DOCUMENT_NODE)
527           {
528             out.write(encodeText(eol));
529           }
530         break;
531       default:
532         System.err.println("Unhandled node type: "+nt);
533       }
534   }
535 
isHTMLElement(Node node, String name)536   boolean isHTMLElement(Node node, String name)
537   {
538     if (node.getNodeType() != Node.ELEMENT_NODE)
539       return false;
540     String localName = node.getLocalName();
541     if (localName == null)
542       localName = node.getNodeName();
543     if (!name.equalsIgnoreCase(localName))
544       return false;
545     String uri = node.getNamespaceURI();
546     return (uri == null || HTML_URIS.contains(uri));
547   }
548 
isDefined(String uri, String prefix)549   boolean isDefined(String uri, String prefix)
550   {
551     if (XMLConstants.XML_NS_URI.equals(uri))
552       return "xml".equals(prefix);
553     if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri))
554       return "xmlns".equals(prefix);
555     if (prefix == null)
556       prefix = "";
557     for (Iterator i = namespaces.iterator(); i.hasNext(); )
558       {
559         Map ctx = (Map) i.next();
560         String val = (String) ctx.get(uri);
561         if (val != null && val.equals(prefix))
562           return true;
563       }
564     return false;
565   }
566 
pushNamespaceContext()567   void pushNamespaceContext()
568   {
569     namespaces.addFirst(new HashMap());
570   }
571 
define(String uri, String prefix)572   String define(String uri, String prefix)
573   {
574     if (namespaces.isEmpty())
575       return prefix;
576     HashMap ctx = (HashMap) namespaces.getFirst();
577     while (ctx.containsValue(prefix))
578       {
579         // Fabricate new prefix
580         prefix = prefix + "_";
581       }
582     ctx.put(uri, prefix);
583     return prefix;
584   }
585 
popNamespaceContext()586   void popNamespaceContext()
587   {
588     namespaces.removeFirst();
589   }
590 
encodeText(String text)591   final byte[] encodeText(String text)
592     throws IOException
593   {
594     encoder.reset();
595     boolean htmlNeedingEncoding =
596       (mode == Stylesheet.OUTPUT_HTML && !htmlEncoded);
597     if (!encoder.canEncode(text) || htmlNeedingEncoding)
598       {
599         // Check each character
600         CPStringBuilder buf = new CPStringBuilder();
601         int len = text.length();
602         for (int i = 0; i < len; i++)
603           {
604             char c = text.charAt(i);
605             if (!encoder.canEncode(c))
606               {
607                 // Replace with character entity reference
608                 String hex = Integer.toHexString((int) c);
609                 buf.append("&#x");
610                 buf.append(hex);
611                 buf.append(';');
612               }
613             else if (htmlNeedingEncoding)
614               {
615                 String entityName = getHTMLCharacterEntity(c);
616                 if (entityName != null)
617                   {
618                     buf.append('&');
619                     buf.append(entityName);
620                     buf.append(';');
621                   }
622                 else
623                   buf.append(c);
624               }
625             else
626               buf.append(c);
627           }
628         text = buf.toString();
629       }
630     ByteBuffer encoded = encoder.encode(CharBuffer.wrap(text));
631     int len = encoded.limit() - encoded.position();
632     if (encoded.hasArray())
633       {
634         byte[] ret = encoded.array();
635         if (ret.length > len)
636           {
637             // Why?
638             byte[] ret2 = new byte[len];
639             System.arraycopy(ret, 0, ret2, 0, len);
640             ret = ret2;
641           }
642         return ret;
643       }
644     encoded.flip();
645     byte[] ret = new byte[len];
646     encoded.get(ret, 0, len);
647     return ret;
648   }
649 
encode(String text, boolean encodeCtl, boolean inAttr)650   String encode(String text, boolean encodeCtl, boolean inAttr)
651   {
652     int len = text.length();
653     CPStringBuilder buf = null;
654     for (int i = 0; i < len; i++)
655       {
656         char c = text.charAt(i);
657         if (c == '<')
658           {
659             if (buf == null)
660               buf = new CPStringBuilder(text.substring(0, i));
661             buf.append("&lt;");
662           }
663         else if (c == '>')
664           {
665             if (buf == null)
666               buf = new CPStringBuilder(text.substring(0, i));
667             buf.append("&gt;");
668           }
669         else if (c == '&')
670           {
671             if (mode == Stylesheet.OUTPUT_HTML && (i + 1) < len &&
672                 text.charAt(i + 1) == '{')
673               {
674                 if (buf != null)
675                   buf.append(c);
676               }
677             else
678               {
679                 if (buf == null)
680                   buf = new CPStringBuilder(text.substring(0, i));
681                 buf.append("&amp;");
682               }
683           }
684         else if (c == '\'' && inAttr)
685           {
686             if (buf == null)
687               buf = new CPStringBuilder(text.substring(0, i));
688             if (mode == Stylesheet.OUTPUT_HTML)
689               // HTML does not define &apos;, use character entity ref
690               buf.append("&#x27;");
691             else
692               buf.append("&apos;");
693           }
694         else if (c == '"' && inAttr)
695           {
696             if (buf == null)
697               buf = new CPStringBuilder(text.substring(0, i));
698             buf.append("&quot;");
699           }
700         else if (encodeCtl)
701           {
702             if (c < 0x20)
703               {
704                 if (buf == null)
705                   buf = new CPStringBuilder(text.substring(0, i));
706                 buf.append('&');
707                 buf.append('#');
708                 buf.append((int) c);
709                 buf.append(';');
710               }
711             else if (buf != null)
712               buf.append(c);
713           }
714         else if (buf != null)
715           buf.append(c);
716       }
717     return (buf == null) ? text : buf.toString();
718   }
719 
toString(Node node)720   String toString(Node node)
721   {
722     ByteArrayOutputStream out = new ByteArrayOutputStream();
723     try
724       {
725         serialize(node, out);
726         return new String(out.toByteArray(), encoding);
727       }
728     catch (IOException e)
729       {
730         throw new RuntimeException(e.getMessage());
731       }
732   }
733 
isHTMLBoolean(Attr attr, String attrName)734   boolean isHTMLBoolean(Attr attr, String attrName)
735   {
736     attrName = attrName.toLowerCase();
737     Node element = attr.getOwnerElement();
738     String elementName = element.getLocalName();
739     if (elementName == null)
740       {
741         elementName = element.getNodeName();
742       }
743     elementName = elementName.toLowerCase();
744     Collection attributes =
745       (Collection) HTML_BOOLEAN_ATTRIBUTES.get(elementName);
746     return (attributes != null && attributes.contains(attrName));
747   }
748 
getHTMLCharacterEntity(char c)749   static String getHTMLCharacterEntity(char c)
750   {
751     // Hardcode these here to avoid loading the HTML DTD
752     switch (c)
753       {
754       case 160: return "nbsp";
755       case 161: return "iexcl";
756       case 162: return "cent";
757       case 163: return "pound";
758       case 164: return "curren";
759       case 165: return "yen";
760       case 166: return "brvbar";
761       case 167: return "sect";
762       case 168: return "uml";
763       case 169: return "copy";
764       case 170: return "ordf";
765       case 171: return "laquo";
766       case 172: return "not";
767       case 173: return "shy";
768       case 174: return "reg";
769       case 175: return "macr";
770       case 176: return "deg";
771       case 177: return "plusmn";
772       case 178: return "sup2";
773       case 179: return "sup3";
774       case 180: return "acute";
775       case 181: return "micro";
776       case 182: return "para";
777       case 183: return "middot";
778       case 184: return "cedil";
779       case 185: return "sup1";
780       case 186: return "ordm";
781       case 187: return "raquo";
782       case 188: return "frac14";
783       case 189: return "frac12";
784       case 190: return "frac34";
785       case 191: return "iquest";
786       case 192: return "Agrave";
787       case 193: return "Aacute";
788       case 194: return "Acirc";
789       case 195: return "Atilde";
790       case 196: return "Auml";
791       case 197: return "Aring";
792       case 198: return "AElig";
793       case 199: return "Ccedil";
794       case 200: return "Egrave";
795       case 201: return "Eacute";
796       case 202: return "Ecirc";
797       case 203: return "Euml";
798       case 204: return "Igrave";
799       case 205: return "Iacute";
800       case 206: return "Icirc";
801       case 207: return "Iuml";
802       case 208: return "ETH";
803       case 209: return "Ntilde";
804       case 210: return "Ograve";
805       case 211: return "Oacute";
806       case 212: return "Ocirc";
807       case 213: return "Otilde";
808       case 214: return "Ouml";
809       case 215: return "times";
810       case 216: return "Oslash";
811       case 217: return "Ugrave";
812       case 218: return "Uacute";
813       case 219: return "Ucirc";
814       case 220: return "Uuml";
815       case 221: return "Yacute";
816       case 222: return "THORN";
817       case 223: return "szlig";
818       case 224: return "agrave";
819       case 225: return "aacute";
820       case 226: return "acirc";
821       case 227: return "atilde";
822       case 228: return "auml";
823       case 229: return "aring";
824       case 230: return "aelig";
825       case 231: return "ccedil";
826       case 232: return "egrave";
827       case 233: return "eacute";
828       case 234: return "ecirc";
829       case 235: return "euml";
830       case 236: return "igrave";
831       case 237: return "iacute";
832       case 238: return "icirc";
833       case 239: return "iuml";
834       case 240: return "eth";
835       case 241: return "ntilde";
836       case 242: return "ograve";
837       case 243: return "oacute";
838       case 244: return "ocirc";
839       case 245: return "otilde";
840       case 246: return "ouml";
841       case 247: return "divide";
842       case 248: return "oslash";
843       case 249: return "ugrave";
844       case 250: return "uacute";
845       case 251: return "ucirc";
846       case 252: return "uuml";
847       case 253: return "yacute";
848       case 254: return "thorn";
849       case 255: return "yuml";
850       default: return null;
851       }
852   }
853 
854 }
855