1 /* SAXEventSink.java --
2    Copyright (C) 1999,2000,2001 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.dom.ls;
39 
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.LinkedList;
43 import java.util.List;
44 import javax.xml.XMLConstants;
45 import org.w3c.dom.Attr;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.DocumentType;
48 import org.w3c.dom.Element;
49 import org.w3c.dom.Entity;
50 import org.w3c.dom.EntityReference;
51 import org.w3c.dom.NamedNodeMap;
52 import org.w3c.dom.Node;
53 import org.w3c.dom.Text;
54 import org.xml.sax.Attributes;
55 import org.xml.sax.ContentHandler;
56 import org.xml.sax.DTDHandler;
57 import org.xml.sax.Locator;
58 import org.xml.sax.SAXException;
59 import org.xml.sax.SAXNotRecognizedException;
60 import org.xml.sax.SAXNotSupportedException;
61 import org.xml.sax.XMLReader;
62 import org.xml.sax.ext.Attributes2;
63 import org.xml.sax.ext.DeclHandler;
64 import org.xml.sax.ext.LexicalHandler;
65 import org.xml.sax.ext.Locator2;
66 import gnu.xml.dom.DomAttr;
67 import gnu.xml.dom.DomDocument;
68 import gnu.xml.dom.DomDoctype;
69 import gnu.xml.dom.DomNode;
70 
71 /**
72  * A SAX content and lexical handler used to construct a DOM document.
73  *
74  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
75  */
76 public class SAXEventSink
77   implements ContentHandler, LexicalHandler, DTDHandler, DeclHandler
78 {
79 
80   private static final String XMLNS_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
81   private static final String XMLNS_PREFIX = XMLConstants.XMLNS_ATTRIBUTE;
82   private static final HashSet PREDEFINED_ENTITIES = new HashSet();
83   static
84   {
85     PREDEFINED_ENTITIES.add("amp");
86     PREDEFINED_ENTITIES.add("lt");
87     PREDEFINED_ENTITIES.add("gt");
88     PREDEFINED_ENTITIES.add("quot");
89     PREDEFINED_ENTITIES.add("apos");
90   }
91 
92   private boolean namespaceAware;
93   boolean ignoreWhitespace;
94   boolean expandEntityReferences;
95   boolean ignoreComments;
96   boolean coalescing;
97 
98   XMLReader reader; // reference back to the parser to get features
99 
100   DomDocument doc; // document being constructed
101   Node ctx; // current context (parent node)
102   LinkedList entityCtx; // entity context
103   List pending; // namespace nodes waiting for a declaring element
104   Locator locator;
105   boolean inCDATA;
106   boolean inDTD;
107   boolean interrupted;
108 
interrupt()109   void interrupt()
110   {
111     interrupted = true;
112   }
113 
getDocument()114   public Document getDocument()
115   {
116     return doc;
117   }
118 
setReader(XMLReader reader)119   public void setReader(XMLReader reader)
120   {
121     this.reader = reader;
122   }
123 
124   // -- ContentHandler2 --
125 
setDocumentLocator(Locator locator)126   public void setDocumentLocator(Locator locator)
127   {
128     this.locator = locator;
129   }
130 
setNamespaceAware(boolean namespaceAware)131   public void setNamespaceAware(boolean namespaceAware)
132   {
133     this.namespaceAware = namespaceAware;
134   }
135 
startDocument()136   public void startDocument()
137     throws SAXException
138   {
139     if (namespaceAware)
140       {
141         pending = new LinkedList();
142       }
143     doc = new DomDocument();
144     doc.setStrictErrorChecking(false);
145     doc.setBuilding(true);
146     doc.setDefaultAttributes(false);
147     ctx = doc;
148 
149     final String FEATURES = "http://xml.org/sax/features/";
150     final String PROPERTIES = "http://xml.org/sax/properties/";
151     final String GNU_PROPERTIES = "http://gnu.org/sax/properties/";
152 
153     if (reader != null)
154       {
155         boolean standalone = reader.getFeature(FEATURES + "is-standalone");
156         doc.setXmlStandalone(standalone);
157         try
158           {
159             String version = (String) reader.getProperty(PROPERTIES +
160                     "document-xml-version");
161             doc.setXmlVersion(version);
162           }
163         catch (SAXNotRecognizedException e)
164           {
165           }
166         catch (SAXNotSupportedException e)
167           {
168           }
169         try
170           {
171               String encoding = (String) reader.getProperty(GNU_PROPERTIES +
172                       "document-xml-encoding");
173               doc.setXmlEncoding(encoding);
174           }
175         catch (SAXNotRecognizedException e)
176           {
177           }
178         catch (SAXNotSupportedException e)
179           {
180           }
181       }
182     if (locator != null && locator instanceof Locator2)
183       {
184         String encoding = ((Locator2) locator).getEncoding();
185         doc.setInputEncoding(encoding);
186       }
187   }
188 
endDocument()189   public void endDocument()
190     throws SAXException
191   {
192     doc.setStrictErrorChecking(true);
193     doc.setBuilding(false);
194     doc.setDefaultAttributes(true);
195     DomDoctype doctype = (DomDoctype) doc.getDoctype();
196     if (doctype != null)
197       {
198         doctype.makeReadonly();
199       }
200     ctx = null;
201     locator = null;
202   }
203 
startPrefixMapping(String prefix, String uri)204   public void startPrefixMapping(String prefix, String uri)
205     throws SAXException
206   {
207     if (namespaceAware)
208       {
209         String nsName = (prefix != null && prefix.length() > 0) ?
210           XMLNS_PREFIX + ":" + prefix : XMLNS_PREFIX;
211         DomAttr ns = (DomAttr) doc.createAttributeNS(XMLNS_URI, nsName);
212         ns.setNodeValue(uri);
213         if (ctx.getNodeType() == Node.ATTRIBUTE_NODE)
214           {
215             // Add to owner element
216             Node target = ((Attr) ctx).getOwnerElement();
217             target.getAttributes().setNamedItemNS(ns);
218           }
219         else
220           {
221             // Add to pending list; namespace node will be inserted when
222             // element is seen
223             pending.add(ns);
224           }
225       }
226   }
227 
endPrefixMapping(String prefix)228   public void endPrefixMapping(String prefix)
229     throws SAXException
230   {
231   }
232 
startElement(String uri, String localName, String qName, Attributes atts)233   public void startElement(String uri, String localName, String qName,
234                            Attributes atts)
235     throws SAXException
236   {
237     if (interrupted)
238       {
239         return;
240       }
241     Element element = createElement(uri, localName, qName, atts);
242     // add element to context
243     ctx.appendChild(element);
244     ctx = element;
245   }
246 
createElement(String uri, String localName, String qName, Attributes atts)247   protected Element createElement(String uri, String localName, String qName,
248                                   Attributes atts)
249     throws SAXException
250   {
251     // create element node
252     Element element = namespaceAware ?
253       doc.createElementNS(uri, qName) :
254       doc.createElement(qName);
255     NamedNodeMap attrs = element.getAttributes();
256     if (namespaceAware && !pending.isEmpty())
257       {
258         // add pending namespace nodes
259         for (Iterator i = pending.iterator(); i.hasNext(); )
260           {
261             Node ns = (Node) i.next();
262             attrs.setNamedItemNS(ns);
263           }
264         pending.clear();
265       }
266     // add attributes
267     int len = atts.getLength();
268     for (int i = 0; i < len; i++)
269       {
270         // create attribute
271         Attr attr = createAttr(atts, i);
272         if (attr != null)
273           {
274             // add attribute to element
275             if (namespaceAware)
276               {
277                 attrs.setNamedItemNS(attr);
278               }
279             else
280               {
281                 attrs.setNamedItem(attr);
282               }
283           }
284       }
285     return element;
286   }
287 
createAttr(Attributes atts, int index)288   protected Attr createAttr(Attributes atts, int index)
289   {
290     DomAttr attr;
291     if (namespaceAware)
292       {
293         String a_uri = atts.getURI(index);
294         String a_qName = atts.getQName(index);
295         attr = (DomAttr) doc.createAttributeNS(a_uri, a_qName);
296       }
297     else
298       {
299         String a_qName = atts.getQName(index);
300         attr = (DomAttr) doc.createAttribute(a_qName);
301       }
302     attr.setNodeValue(atts.getValue(index));
303     if (atts instanceof Attributes2)
304       {
305         Attributes2 atts2 = (Attributes2) atts;
306         // TODO attr.setDeclared(atts2.isDeclared(index));
307         attr.setSpecified(atts2.isSpecified(index));
308       }
309     return attr;
310   }
311 
endElement(String uri, String localName, String qName)312   public void endElement(String uri, String localName, String qName)
313     throws SAXException
314   {
315     if (interrupted)
316       {
317         return;
318       }
319     if (namespaceAware)
320       {
321         pending.clear();
322       }
323     ctx = ctx.getParentNode();
324   }
325 
characters(char[] c, int off, int len)326   public void characters(char[] c, int off, int len)
327     throws SAXException
328   {
329     if (interrupted || len < 1)
330       {
331         return;
332       }
333     ctx.appendChild(createText(c, off, len));
334   }
335 
createText(char[] c, int off, int len)336   protected Text createText(char[] c, int off, int len)
337     throws SAXException
338   {
339     Text text = (inCDATA && !coalescing) ?
340       doc.createCDATASection(new String(c, off, len)) :
341       doc.createTextNode(new String(c, off, len));
342     return text;
343   }
344 
ignorableWhitespace(char[] c, int off, int len)345   public void ignorableWhitespace(char[] c, int off, int len)
346     throws SAXException
347   {
348     if (interrupted)
349       {
350         return;
351       }
352     if (!ignoreWhitespace)
353       {
354         characters(c, off, len);
355       }
356   }
357 
processingInstruction(String target, String data)358   public void processingInstruction(String target, String data)
359     throws SAXException
360   {
361     if (interrupted)
362       {
363         return;
364       }
365     Node pi = createProcessingInstruction(target, data);
366     ctx.appendChild(pi);
367   }
368 
createProcessingInstruction(String target, String data)369   protected Node createProcessingInstruction(String target, String data)
370   {
371     return doc.createProcessingInstruction(target, data);
372   }
373 
skippedEntity(String name)374   public void skippedEntity(String name)
375     throws SAXException
376   {
377     // This callback is totally pointless
378   }
379 
380   // -- LexicalHandler --
381 
startDTD(String name, String publicId, String systemId)382   public void startDTD(String name, String publicId, String systemId)
383     throws SAXException
384   {
385     if (interrupted)
386       {
387         return;
388       }
389     Node doctype = createDocumentType(name, publicId, systemId);
390     doc.appendChild(doctype);
391     ctx = doctype;
392     inDTD = true;
393   }
394 
createDocumentType(String name, String publicId, String systemId)395   protected Node createDocumentType(String name, String publicId,
396                                     String systemId)
397   {
398     return new DomDoctype(doc, name, publicId, systemId);
399   }
400 
endDTD()401   public void endDTD()
402     throws SAXException
403   {
404     if (interrupted)
405       {
406         return;
407       }
408     inDTD = false;
409     ctx = ctx.getParentNode();
410   }
411 
startEntity(String name)412   public void startEntity(String name)
413     throws SAXException
414   {
415     if (interrupted)
416       return;
417     DocumentType doctype = doc.getDoctype();
418     if (doctype == null)
419       {
420         throw new SAXException("SAX parser error: " +
421                                "reference to entity in undeclared doctype");
422       }
423     if ("[dtd]".equals(name) || name.charAt(0) == '%')
424       return;
425     if (PREDEFINED_ENTITIES.contains(name))
426       return;
427     // Get entity
428     NamedNodeMap entities = doctype.getEntities();
429     Entity entity = (Entity) entities.getNamedItem(name);
430     if (entity == null)
431       {
432         throw new SAXException("SAX parser error: " +
433                                "reference to undeclared entity: " + name);
434       }
435     EntityReference ref = doc.createEntityReference(name);
436     // DomDocument populates with the entity replacement text, remove this
437     Node child = ref.getFirstChild();
438     while (child != null)
439       {
440         Node nextChild = child.getNextSibling();
441         ref.removeChild(child);
442         child = nextChild;
443       }
444     ctx.appendChild(ref);
445     ctx = ref;
446   }
447 
endEntity(String name)448   public void endEntity(String name)
449     throws SAXException
450   {
451     if (interrupted)
452       return;
453     if ("[dtd]".equals(name) || name.charAt(0) == '%')
454       return;
455     if (PREDEFINED_ENTITIES.contains(name))
456       return;
457     // Get entity reference
458     EntityReference ref = (EntityReference) ctx;
459     if (!ref.getNodeName().equals(name))
460       throw new SAXException("expecting end of "+ref.getNodeName()+" entity");
461     ctx = ctx.getParentNode();
462     if (ref instanceof DomNode)
463       ((DomNode) ref).makeReadonly();
464     if (expandEntityReferences)
465       {
466         // Move entity content from reference node onto context
467         Node child = ref.getFirstChild();
468         while (child != null)
469           {
470             Node nextChild = child.getNextSibling();
471             ctx.appendChild(child);
472             child = nextChild;
473           }
474         ctx.removeChild(ref);
475       }
476   }
477 
startCDATA()478   public void startCDATA()
479     throws SAXException
480   {
481     inCDATA = true;
482   }
483 
endCDATA()484   public void endCDATA()
485     throws SAXException
486   {
487     inCDATA = false;
488   }
489 
comment(char[] c, int off, int len)490   public void comment(char[] c, int off, int len)
491     throws SAXException
492   {
493     if (interrupted)
494       {
495         return;
496       }
497     Node comment = createComment(c, off, len);
498     ctx.appendChild(comment);
499   }
500 
createComment(char[] c, int off, int len)501   protected Node createComment(char[] c, int off, int len)
502   {
503     return doc.createComment(new String(c, off, len));
504   }
505 
506   // -- DTDHandler --
507 
notationDecl(String name, String publicId, String systemId)508   public void notationDecl(String name, String publicId, String systemId)
509     throws SAXException
510   {
511     if (interrupted)
512       {
513         return;
514       }
515     if (!inDTD)
516       throw new SAXException("notation decl outside DTD");
517     DomDoctype doctype = (DomDoctype) ctx;
518     doctype.declareNotation(name, publicId, systemId);
519   }
520 
unparsedEntityDecl(String name, String publicId, String systemId, String notationName)521   public void unparsedEntityDecl(String name, String publicId, String systemId,
522                                  String notationName)
523     throws SAXException
524   {
525     if (interrupted)
526       {
527         return;
528       }
529     if (!inDTD)
530       throw new SAXException("unparsed entity decl outside DTD");
531     DomDoctype doctype = (DomDoctype) ctx;
532     Entity entity = doctype.declareEntity(name, publicId, systemId,
533                                           notationName);
534   }
535 
536   // -- DeclHandler --
537 
elementDecl(String name, String model)538   public void elementDecl(String name, String model)
539     throws SAXException
540   {
541     if (interrupted)
542       {
543         return;
544       }
545     if (!inDTD)
546       throw new SAXException("element decl outside DTD");
547     // Ignore fake element declarations generated by ValidationConsumer.
548     // If an element is not really declared in the DTD it will not be
549     // declared in the document model.
550     if (!(ctx instanceof DomDoctype))
551       {
552         return;
553       }
554     DomDoctype doctype = (DomDoctype) ctx;
555     doctype.elementDecl(name, model);
556   }
557 
attributeDecl(String eName, String aName, String type, String mode, String value)558   public void attributeDecl(String eName, String aName, String type,
559                             String mode, String value)
560     throws SAXException
561   {
562     if (interrupted)
563       {
564         return;
565       }
566     if (!inDTD)
567       throw new SAXException("attribute decl outside DTD");
568     DomDoctype doctype = (DomDoctype) ctx;
569     doctype.attributeDecl(eName, aName, type, mode, value);
570   }
571 
internalEntityDecl(String name, String value)572   public void internalEntityDecl(String name, String value)
573     throws SAXException
574   {
575     if (interrupted)
576       {
577         return;
578       }
579     if (!inDTD)
580       throw new SAXException("internal entity decl outside DTD");
581     DomDoctype doctype = (DomDoctype) ctx;
582     Entity entity = doctype.declareEntity(name, null, null, null);
583     if (entity != null)
584       {
585         Node text = doc.createTextNode(value);
586         entity.appendChild(text);
587       }
588   }
589 
externalEntityDecl(String name, String publicId, String systemId)590   public void externalEntityDecl(String name, String publicId, String systemId)
591     throws SAXException
592   {
593     if (interrupted)
594       {
595         return;
596       }
597     if (!inDTD)
598       throw new SAXException("external entity decl outside DTD");
599     DomDoctype doctype = (DomDoctype) ctx;
600     Entity entity = doctype.declareEntity(name, publicId, systemId, null);
601   }
602 
603 }
604