001 /*
002 * DomConsumer.java
003 * Copyright (C) 1999,2000,2001 The Free Software Foundation
004 *
005 * This file is part of GNU JAXP, a library.
006 *
007 * GNU JAXP is free software; you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as published by
009 * the Free Software Foundation; either version 2 of the License, or
010 * (at your option) any later version.
011 *
012 * GNU JAXP is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with this program; if not, write to the Free Software
019 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020 *
021 * Linking this library statically or dynamically with other modules is
022 * making a combined work based on this library. Thus, the terms and
023 * conditions of the GNU General Public License cover the whole
024 * combination.
025 *
026 * As a special exception, the copyright holders of this library give you
027 * permission to link this library with independent modules to produce an
028 * executable, regardless of the license terms of these independent
029 * modules, and to copy and distribute the resulting executable under
030 * terms of your choice, provided that you also meet, for each linked
031 * independent module, the terms and conditions of the license of that
032 * module. An independent module is a module which is not derived from
033 * or based on this library. If you modify this library, you may extend
034 * this exception to your version of the library, but you are not
035 * obliged to do so. If you do not wish to do so, delete this
036 * exception statement from your version.
037 */
038
039 package fi.iki.hsivonen.gnu.xml.pipeline;
040
041 import gnu.xml.dom.DomDocument;
042 import gnu.xml.pipeline.EventConsumer;
043 import gnu.xml.pipeline.EventFilter;
044 import gnu.xml.pipeline.NSFilter;
045 import gnu.xml.pipeline.TeeConsumer;
046 import gnu.xml.util.DomParser;
047
048 import org.w3c.dom.Attr;
049 import org.w3c.dom.CDATASection;
050 import org.w3c.dom.CharacterData;
051 import org.w3c.dom.DOMImplementation;
052 import org.w3c.dom.Document;
053 import org.w3c.dom.Element;
054 import org.w3c.dom.EntityReference;
055 import org.w3c.dom.Node;
056 import org.w3c.dom.ProcessingInstruction;
057 import org.w3c.dom.Text;
058 import org.xml.sax.Attributes;
059 import org.xml.sax.ContentHandler;
060 import org.xml.sax.DTDHandler;
061 import org.xml.sax.ErrorHandler;
062 import org.xml.sax.Locator;
063 import org.xml.sax.SAXException;
064 import org.xml.sax.SAXNotRecognizedException;
065 import org.xml.sax.SAXParseException;
066 import org.xml.sax.ext.DeclHandler;
067 import org.xml.sax.ext.LexicalHandler;
068 import org.xml.sax.helpers.AttributesImpl;
069
070
071 /**
072 * This consumer builds a DOM Document from its input, acting either as a
073 * pipeline terminus or as an intermediate buffer. When a document's worth
074 * of events has been delivered to this consumer, that document is read with
075 * a {@link DomParser} and sent to the next consumer. It is also available
076 * as a read-once property.
077 *
078 * <p>The DOM tree is constructed as faithfully as possible. There are some
079 * complications since a DOM should expose behaviors that can't be implemented
080 * without API backdoors into that DOM, and because some SAX parsers don't
081 * report all the information that DOM permits to be exposed. The general
082 * problem areas involve information from the Document Type Declaration (DTD).
083 * DOM only represents a limited subset, but has some behaviors that depend
084 * on much deeper knowledge of a document's DTD. You shouldn't have much to
085 * worry about unless you change handling of "noise" nodes from its default
086 * setting (which ignores them all); note if you use JAXP to populate your
087 * DOM trees, it wants to save "noise" nodes by default. (Such nodes include
088 * ignorable whitespace, comments, entity references and CDATA boundaries.)
089 * Otherwise, your
090 * main worry will be if you use a SAX parser that doesn't flag ignorable
091 * whitespace unless it's validating (few don't).
092 *
093 * <p> The SAX2 events used as input must contain XML Names for elements
094 * and attributes, with original prefixes. In SAX2,
095 * this is optional unless the "namespace-prefixes" parser feature is set.
096 * Moreover, many application components won't provide completely correct
097 * structures anyway. <em>Before you convert a DOM to an output document,
098 * you should plan to postprocess it to create or repair such namespace
099 * information.</em> The {@link NSFilter} pipeline stage does such work.
100 *
101 * <p> <em>Note: changes late in DOM L2 process made it impractical to
102 * attempt to create the DocumentType node in any implementation-neutral way,
103 * much less to populate it (L1 didn't support even creating such nodes).
104 * To create and populate such a node, subclass the inner
105 * {@link DomConsumer.Handler} class and teach it about the backdoors into
106 * whatever DOM implementation you want. It's possible that some revised
107 * DOM API (L3?) will make this problem solvable again. </em>
108 *
109 * @see DomParser
110 *
111 * @author David Brownell
112 */
113 public class DomConsumer implements EventConsumer
114 {
115 private Class<DomDocument> domImpl;
116
117 private boolean hidingCDATA = true;
118 private boolean hidingComments = true;
119 private boolean hidingWhitespace = true;
120 private boolean hidingReferences = true;
121
122 private Handler handler;
123 private ErrorHandler errHandler;
124
125 private EventConsumer next;
126
127 // FIXME: this can't be a generic pipeline stage just now,
128 // since its input became a Class not a String (to be turned
129 // into a class, using the right class loader)
130
131
132 /**
133 * Configures this pipeline terminus to use the specified implementation
134 * of DOM when constructing its result value.
135 *
136 * @param impl class implementing {@link org.w3c.dom.Document Document}
137 * which publicly exposes a default constructor
138 *
139 * @exception SAXException when there is a problem creating an
140 * empty DOM document using the specified implementation
141 */
142 public DomConsumer (Class<DomDocument> impl)
143 throws SAXException
144 {
145 domImpl = impl;
146 handler = new Handler (this);
147 }
148
149 /**
150 * This is the hook through which a subclass provides a handler
151 * which knows how to access DOM extensions, specific to some
152 * implementation, to record additional data in a DOM.
153 * Treat this as part of construction; don't call it except
154 * before (or between) parses.
155 */
156 protected void setHandler (Handler h)
157 {
158 handler = h;
159 }
160
161
162 private Document emptyDocument ()
163 throws SAXException
164 {
165 try {
166 return domImpl.newInstance ();
167 } catch (IllegalAccessException e) {
168 throw new SAXException ("can't access constructor: "
169 + e.getMessage ());
170 } catch (InstantiationException e) {
171 throw new SAXException ("can't instantiate Document: "
172 + e.getMessage ());
173 }
174 }
175
176
177 /**
178 * Configures this consumer as a buffer/filter, using the specified
179 * DOM implementation when constructing its result value.
180 *
181 * <p> This event consumer acts as a buffer and filter, in that it
182 * builds a DOM tree and then writes it out when <em>endDocument</em>
183 * is invoked. Because of the limitations of DOM, much information
184 * will as a rule not be seen in that replay. To get a full fidelity
185 * copy of the input event stream, use a {@link TeeConsumer}.
186 *
187 * @param impl class implementing {@link org.w3c.dom.Document Document}
188 * which publicly exposes a default constructor
189 * @param next receives a "replayed" sequence of parse events when
190 * the <em>endDocument</em> method is invoked.
191 *
192 * @exception SAXException when there is a problem creating an
193 * empty DOM document using the specified DOM implementation
194 */
195 public DomConsumer (Class impl, EventConsumer n)
196 throws SAXException
197 {
198 this (impl);
199 next = n;
200 }
201
202
203 /**
204 * Returns the document constructed from the preceding
205 * sequence of events. This method should not be
206 * used again until another sequence of events has been
207 * given to this EventConsumer.
208 */
209 final public Document getDocument ()
210 {
211 return handler.clearDocument ();
212 }
213
214 public void setErrorHandler (ErrorHandler handler)
215 {
216 errHandler = handler;
217 }
218
219
220 /**
221 * Returns true if the consumer is hiding entity references nodes
222 * (the default), and false if EntityReference nodes should
223 * instead be created. Such EntityReference nodes will normally be
224 * empty, unless an implementation arranges to populate them and then
225 * turn them back into readonly objects.
226 *
227 * @see #setHidingReferences
228 */
229 final public boolean isHidingReferences ()
230 { return hidingReferences; }
231
232 /**
233 * Controls whether the consumer will hide entity expansions,
234 * or will instead mark them with entity reference nodes.
235 *
236 * @see #isHidingReferences
237 * @param flag False if entity reference nodes will appear
238 */
239 final public void setHidingReferences (boolean flag)
240 { hidingReferences = flag; }
241
242
243 /**
244 * Returns true if the consumer is hiding comments (the default),
245 * and false if they should be placed into the output document.
246 *
247 * @see #setHidingComments
248 */
249 public final boolean isHidingComments ()
250 { return hidingComments; }
251
252 /**
253 * Controls whether the consumer is hiding comments.
254 *
255 * @see #isHidingComments
256 */
257 public final void setHidingComments (boolean flag)
258 { hidingComments = flag; }
259
260
261 /**
262 * Returns true if the consumer is hiding ignorable whitespace
263 * (the default), and false if such whitespace should be placed
264 * into the output document as children of element nodes.
265 *
266 * @see #setHidingWhitespace
267 */
268 public final boolean isHidingWhitespace ()
269 { return hidingWhitespace; }
270
271 /**
272 * Controls whether the consumer hides ignorable whitespace
273 *
274 * @see #isHidingComments
275 */
276 public final void setHidingWhitespace (boolean flag)
277 { hidingWhitespace = flag; }
278
279
280 /**
281 * Returns true if the consumer is saving CDATA boundaries, or
282 * false (the default) otherwise.
283 *
284 * @see #setHidingCDATA
285 */
286 final public boolean isHidingCDATA ()
287 { return hidingCDATA; }
288
289 /**
290 * Controls whether the consumer will save CDATA boundaries.
291 *
292 * @see #isHidingCDATA
293 * @param flag True to treat CDATA text differently from other
294 * text nodes
295 */
296 final public void setHidingCDATA (boolean flag)
297 { hidingCDATA = flag; }
298
299
300
301 /** Returns the document handler being used. */
302 final public ContentHandler getContentHandler ()
303 { return handler; }
304
305 /** Returns the DTD handler being used. */
306 final public DTDHandler getDTDHandler ()
307 { return handler; }
308
309 /**
310 * Returns the lexical handler being used.
311 * (DOM construction can't really use declaration handlers.)
312 */
313 final public Object getProperty (String id)
314 throws SAXNotRecognizedException
315 {
316 if ("http://xml.org/sax/properties/lexical-handler".equals (id))
317 return handler;
318 if ("http://xml.org/sax/properties/declaration-handler".equals (id))
319 return handler;
320 throw new SAXNotRecognizedException (id);
321 }
322
323 EventConsumer getNext () { return next; }
324
325 ErrorHandler getErrorHandler () { return errHandler; }
326
327 /**
328 * Class used to intercept various parsing events and use them to
329 * populate a DOM document. Subclasses would typically know and use
330 * backdoors into specific DOM implementations, used to implement
331 * DTD-related functionality.
332 *
333 * <p> Note that if this ever throws a DOMException (runtime exception)
334 * that will indicate a bug in the DOM (e.g. doesn't support something
335 * per specification) or the parser (e.g. emitted an illegal name, or
336 * accepted illegal input data). </p>
337 */
338 public static class Handler
339 implements ContentHandler, LexicalHandler,
340 DTDHandler, DeclHandler
341 {
342 protected DomConsumer consumer;
343
344 private DOMImplementation impl;
345 private Document document;
346 private boolean isL2;
347
348 private Locator locator;
349 private Node top;
350 private boolean inCDATA;
351 private boolean mergeCDATA;
352 private boolean inDTD;
353 private String currentEntity;
354
355 private boolean recreatedAttrs;
356 private AttributesImpl attributes = new AttributesImpl ();
357
358 /**
359 * Subclasses may use SAX2 events to provide additional
360 * behaviors in the resulting DOM.
361 */
362 protected Handler (DomConsumer consumer)
363 throws SAXException
364 {
365 this.consumer = consumer;
366 document = consumer.emptyDocument ();
367 impl = document.getImplementation ();
368 isL2 = impl.hasFeature ("XML", "2.0");
369 }
370
371 private void fatal (String message, Exception x)
372 throws SAXException
373 {
374 SAXParseException e;
375 ErrorHandler errHandler = consumer.getErrorHandler ();
376
377 if (locator == null)
378 e = new SAXParseException (message, null, null, -1, -1, x);
379 else
380 e = new SAXParseException (message, locator, x);
381 if (errHandler != null)
382 errHandler.fatalError (e);
383 throw e;
384 }
385
386 /**
387 * Returns and forgets the document produced. If the handler is
388 * reused, a new document may be created.
389 */
390 Document clearDocument ()
391 {
392 Document retval = document;
393 document = null;
394 locator = null;
395 return retval;
396 }
397
398 /**
399 * Returns the document under construction.
400 */
401 protected Document getDocument ()
402 { return document; }
403
404 /**
405 * Returns the current node being populated. This is usually
406 * an Element or Document, but it might be an EntityReference
407 * node if some implementation-specific code knows how to put
408 * those into the result tree and later mark them as readonly.
409 */
410 protected Node getTop ()
411 { return top; }
412
413
414 // SAX1
415 public void setDocumentLocator (Locator locator)
416 {
417 this.locator = locator;
418 }
419
420 // SAX1
421 public void startDocument ()
422 throws SAXException
423 {
424 if (document == null)
425 try {
426 if (isL2) {
427 // couple to original implementation
428 document = impl.createDocument (null, "foo", null);
429 document.removeChild (document.getFirstChild ());
430 } else {
431 document = consumer.emptyDocument ();
432 }
433 } catch (Exception e) {
434 fatal ("DOM create document", e);
435 }
436 top = document;
437 }
438
439 // ContentHandler2
440 public void xmlDecl(String version,
441 String encoding,
442 boolean standalone,
443 String inputEncoding)
444 throws SAXException
445 {
446 if (document != null)
447 {
448 //document.setXmlVersion(version);
449 //document.setXmlStandalone(standalone);
450 }
451 }
452
453 // SAX1
454 public void endDocument ()
455 throws SAXException
456 {
457 try {
458 if (consumer.getNext () != null && document != null) {
459 DomParser parser = new DomParser (document);
460
461 EventFilter.bind (parser, consumer.getNext ());
462 parser.parse ("ignored");
463 }
464 } finally {
465 top = null;
466 }
467 }
468
469 // SAX1
470 public void processingInstruction (String target, String data)
471 throws SAXException
472 {
473 // we can't create populated entity ref nodes using
474 // only public DOM APIs (they've got to be readonly)
475 if (currentEntity != null)
476 return;
477
478 ProcessingInstruction pi;
479
480 if (isL2
481 // && consumer.isUsingNamespaces ()
482 && target.indexOf (':') != -1)
483 namespaceError (
484 "PI target name is namespace nonconformant: "
485 + target);
486 if (inDTD)
487 return;
488 pi = document.createProcessingInstruction (target, data);
489 top.appendChild (pi);
490 }
491
492 /**
493 * Subclasses may overrride this method to provide a more efficient
494 * way to construct text nodes.
495 * Typically, copying the text into a single character array will
496 * be more efficient than doing that as well as allocating other
497 * needed for a String, including an internal StringBuilder.
498 * Those additional memory and CPU costs can be incurred later,
499 * if ever needed.
500 * Unfortunately the standard DOM factory APIs encourage those costs
501 * to be incurred early.
502 */
503 protected Text createText (
504 boolean isCDATA,
505 char ch [],
506 int start,
507 int length
508 ) {
509 String value = new String (ch, start, length);
510
511 if (isCDATA)
512 return document.createCDATASection (value);
513 else
514 return document.createTextNode (value);
515 }
516
517 // SAX1
518 public void characters (char ch [], int start, int length)
519 throws SAXException
520 {
521 // we can't create populated entity ref nodes using
522 // only public DOM APIs (they've got to be readonly
523 // at creation time)
524 if (currentEntity != null)
525 return;
526
527 Node lastChild = top.getLastChild ();
528
529 // merge consecutive text or CDATA nodes if appropriate.
530 if (lastChild instanceof Text) {
531 if (consumer.isHidingCDATA ()
532 // consecutive Text content ... always merge
533 || (!inCDATA
534 && !(lastChild instanceof CDATASection))
535 // consecutive CDATASection content ... don't
536 // merge between sections, only within them
537 || (inCDATA && mergeCDATA
538 && lastChild instanceof CDATASection)
539 ) {
540 CharacterData last = (CharacterData) lastChild;
541 String value = new String (ch, start, length);
542
543 last.appendData (value);
544 return;
545 }
546 }
547 if (inCDATA && !consumer.isHidingCDATA ()) {
548 top.appendChild (createText (true, ch, start, length));
549 mergeCDATA = true;
550 } else
551 top.appendChild (createText (false, ch, start, length));
552 }
553
554 // SAX2
555 public void skippedEntity (String name)
556 throws SAXException
557 {
558 // this callback is useless except to report errors, since
559 // we can't know if the ref was in content, within an
560 // attribute, within a declaration ... only one of those
561 // cases supports more intelligent action than a panic.
562 fatal ("skipped entity: " + name, null);
563 }
564
565 // SAX2
566 public void startPrefixMapping (String prefix, String uri)
567 throws SAXException
568 {
569 // reconstruct "xmlns" attributes deleted by all
570 // SAX2 parsers without "namespace-prefixes" = true
571 if ("".equals (prefix))
572 attributes.addAttribute ("", "", "xmlns",
573 "CDATA", uri);
574 else
575 attributes.addAttribute ("", "", "xmlns:" + prefix,
576 "CDATA", uri);
577 recreatedAttrs = true;
578 }
579
580 // SAX2
581 public void endPrefixMapping (String prefix)
582 throws SAXException
583 { }
584
585 // SAX2
586 public void startElement (
587 String uri,
588 String localName,
589 String qName,
590 Attributes atts
591 ) throws SAXException
592 {
593 // we can't create populated entity ref nodes using
594 // only public DOM APIs (they've got to be readonly)
595 if (currentEntity != null)
596 return;
597
598 // parser discarded basic information; DOM tree isn't writable
599 // without massaging to assign prefixes to all nodes.
600 // the "NSFilter" class does that massaging.
601 if (qName.length () == 0)
602 qName = localName;
603
604
605 Element element;
606 int length = atts.getLength ();
607
608 if (!isL2) {
609 element = document.createElement (qName);
610
611 // first the explicit attributes ...
612 length = atts.getLength ();
613 for (int i = 0; i < length; i++)
614 element.setAttribute (atts.getQName (i),
615 atts.getValue (i));
616 // ... then any recreated ones (DOM deletes duplicates)
617 if (recreatedAttrs) {
618 recreatedAttrs = false;
619 length = attributes.getLength ();
620 for (int i = 0; i < length; i++)
621 element.setAttribute (attributes.getQName (i),
622 attributes.getValue (i));
623 attributes.clear ();
624 }
625
626 top.appendChild (element);
627 top = element;
628 return;
629 }
630
631 // For an L2 DOM when namespace use is enabled, use
632 // createElementNS/createAttributeNS except when
633 // (a) it's an element in the default namespace, or
634 // (b) it's an attribute with no prefix
635 String namespace;
636
637 if (localName.length () != 0)
638 namespace = (uri.length () == 0) ? null : uri;
639 else
640 namespace = getNamespace (getPrefix (qName), atts);
641
642 if (namespace == null)
643 element = document.createElement (qName);
644 else
645 element = document.createElementNS (namespace, qName);
646
647 populateAttributes (element, atts);
648 if (recreatedAttrs) {
649 recreatedAttrs = false;
650 // ... DOM deletes any duplicates
651 populateAttributes (element, attributes);
652 attributes.clear ();
653 }
654
655 top.appendChild (element);
656 top = element;
657 }
658
659 final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
660
661 private void populateAttributes (Element element, Attributes attrs)
662 throws SAXParseException
663 {
664 int length = attrs.getLength ();
665
666 for (int i = 0; i < length; i++) {
667 String type = attrs.getType (i);
668 String value = attrs.getValue (i);
669 String name = attrs.getQName (i);
670 String local = attrs.getLocalName (i);
671 String uri = attrs.getURI (i);
672
673 // parser discarded basic information, DOM tree isn't writable
674 if (name.length () == 0)
675 name = local;
676
677 // all attribute types other than these three may not
678 // contain scoped names... enumerated attributes get
679 // reported as NMTOKEN, except for NOTATION values
680 if (!("CDATA".equals (type)
681 || "NMTOKEN".equals (type)
682 || "NMTOKENS".equals (type))) {
683 if (value.indexOf (':') != -1) {
684 namespaceError (
685 "namespace nonconformant attribute value: "
686 + "<" + element.getNodeName ()
687 + " " + name + "='" + value + "' ...>");
688 }
689 }
690
691 // xmlns="" is legal (undoes default NS)
692 // xmlns:foo="" is illegal
693 String prefix = getPrefix (name);
694 String namespace;
695
696 if ("xmlns".equals (prefix)) {
697 if ("".equals (value))
698 namespaceError ("illegal null namespace decl, " + name);
699 namespace = xmlnsURI;
700 } else if ("xmlns".equals (name))
701 namespace = xmlnsURI;
702
703 else if (prefix == null)
704 namespace = null;
705 else if (!"".equals(uri) && uri.length () != 0)
706 namespace = uri;
707 else
708 namespace = getNamespace (prefix, attrs);
709
710 if (namespace == null)
711 element.setAttribute (name, value);
712 else
713 element.setAttributeNS (namespace, name, value);
714 }
715 }
716
717 private String getPrefix (String name)
718 {
719 int temp;
720
721 if ((temp = name.indexOf (':')) > 0)
722 return name.substring (0, temp);
723 return null;
724 }
725
726 // used with SAX1-level parser output
727 private String getNamespace (String prefix, Attributes attrs)
728 throws SAXParseException
729 {
730 String namespace;
731 String decl;
732
733 // defaulting
734 if (prefix == null) {
735 decl = "xmlns";
736 namespace = attrs.getValue (decl);
737 if ("".equals (namespace))
738 return null;
739 else if (namespace != null)
740 return namespace;
741
742 // "xmlns" is like a keyword
743 // ... according to the Namespace REC, but DOM L2 CR2+
744 // and Infoset violate that by assigning a namespace.
745 // that conflict is resolved elsewhere.
746 } else if ("xmlns".equals (prefix))
747 return null;
748
749 // "xml" prefix is fixed
750 else if ("xml".equals (prefix))
751 return "http://www.w3.org/XML/1998/namespace";
752
753 // otherwise, expect a declaration
754 else {
755 decl = "xmlns:" + prefix;
756 namespace = attrs.getValue (decl);
757 }
758
759 // if we found a local declaration, great
760 if (namespace != null)
761 return namespace;
762
763
764 // ELSE ... search up the tree we've been building
765 for (Node n = top;
766 n != null && n.getNodeType () != Node.DOCUMENT_NODE;
767 n = n.getParentNode ()) {
768 if (n.getNodeType () == Node.ENTITY_REFERENCE_NODE)
769 continue;
770 Element e = (Element) n;
771 Attr attr = e.getAttributeNode (decl);
772 if (attr != null)
773 return attr.getNodeValue ();
774 }
775 // see above re "xmlns" as keyword
776 if ("xmlns".equals (decl))
777 return null;
778
779 namespaceError ("Undeclared namespace prefix: " + prefix);
780 return null;
781 }
782
783 // SAX2
784 public void endElement (String uri, String localName, String qName)
785 throws SAXException
786 {
787 // we can't create populated entity ref nodes using
788 // only public DOM APIs (they've got to be readonly)
789 if (currentEntity != null)
790 return;
791
792 top = top.getParentNode ();
793 }
794
795 // SAX1 (mandatory reporting if validating)
796 public void ignorableWhitespace (char ch [], int start, int length)
797 throws SAXException
798 {
799 if (consumer.isHidingWhitespace ())
800 return;
801 characters (ch, start, length);
802 }
803
804 // SAX2 lexical event
805 public void startCDATA ()
806 throws SAXException
807 {
808 inCDATA = true;
809 // true except for the first fragment of a cdata section
810 mergeCDATA = false;
811 }
812
813 // SAX2 lexical event
814 public void endCDATA ()
815 throws SAXException
816 {
817 inCDATA = false;
818 }
819
820 // SAX2 lexical event
821 //
822 // this SAX2 callback merges two unrelated things:
823 // - Declaration of the root element type ... belongs with
824 // the other DTD declaration methods, NOT HERE.
825 // - IDs for the optional external subset ... belongs here
826 // with other lexical information.
827 //
828 // ...and it doesn't include the internal DTD subset, desired
829 // both to support DOM L2 and to enable "pass through" processing
830 //
831 public void startDTD (String name, String publicId, String SystemId)
832 throws SAXException
833 {
834 // need to filter out comments and PIs within the DTD
835 inDTD = true;
836 }
837
838 // SAX2 lexical event
839 public void endDTD ()
840 throws SAXException
841 {
842 inDTD = false;
843 }
844
845 // SAX2 lexical event
846 public void comment (char ch [], int start, int length)
847 throws SAXException
848 {
849 Node comment;
850
851 // we can't create populated entity ref nodes using
852 // only public DOM APIs (they've got to be readonly)
853 if (consumer.isHidingComments ()
854 || inDTD
855 || currentEntity != null)
856 return;
857 comment = document.createComment (new String (ch, start, length));
858 top.appendChild (comment);
859 }
860
861 /**
862 * May be overridden by subclasses to return true, indicating
863 * that entity reference nodes can be populated and then made
864 * read-only.
865 */
866 public boolean canPopulateEntityRefs ()
867 { return false; }
868
869 // SAX2 lexical event
870 public void startEntity (String name)
871 throws SAXException
872 {
873 // are we ignoring what would be contents of an
874 // entity ref, since we can't populate it?
875 if (currentEntity != null)
876 return;
877
878 // Are we hiding all entity boundaries?
879 if (consumer.isHidingReferences ())
880 return;
881
882 // SAX2 shows parameter entities; DOM hides them
883 if (name.charAt (0) == '%' || "[dtd]".equals (name))
884 return;
885
886 // Since we can't create a populated entity ref node in any
887 // standard way, we create an unpopulated one.
888 EntityReference ref = document.createEntityReference (name);
889 top.appendChild (ref);
890 top = ref;
891
892 // ... allowing subclasses to populate them
893 if (!canPopulateEntityRefs ())
894 currentEntity = name;
895 }
896
897 // SAX2 lexical event
898 public void endEntity (String name)
899 throws SAXException
900 {
901 if (name.charAt (0) == '%' || "[dtd]".equals (name))
902 return;
903 if (name.equals (currentEntity))
904 currentEntity = null;
905 if (!consumer.isHidingReferences ())
906 top = top.getParentNode ();
907 }
908
909
910 // SAX1 DTD event
911 public void notationDecl (
912 String name,
913 String publicId, String SystemId
914 ) throws SAXException
915 {
916 /* IGNORE -- no public DOM API lets us store these
917 * into the doctype node
918 */
919 }
920
921 // SAX1 DTD event
922 public void unparsedEntityDecl (
923 String name,
924 String publicId, String SystemId,
925 String notationName
926 ) throws SAXException
927 {
928 /* IGNORE -- no public DOM API lets us store these
929 * into the doctype node
930 */
931 }
932
933 // SAX2 declaration event
934 public void elementDecl (String name, String model)
935 throws SAXException
936 {
937 /* IGNORE -- no content model support in DOM L2 */
938 }
939
940 // SAX2 declaration event
941 public void attributeDecl (
942 String eName,
943 String aName,
944 String type,
945 String mode,
946 String value
947 ) throws SAXException
948 {
949 /* IGNORE -- no attribute model support in DOM L2 */
950 }
951
952 // SAX2 declaration event
953 public void internalEntityDecl (String name, String value)
954 throws SAXException
955 {
956 /* IGNORE -- no public DOM API lets us store these
957 * into the doctype node
958 */
959 }
960
961 // SAX2 declaration event
962 public void externalEntityDecl (
963 String name,
964 String publicId,
965 String SystemId
966 ) throws SAXException
967 {
968 /* IGNORE -- no public DOM API lets us store these
969 * into the doctype node
970 */
971 }
972
973 //
974 // These really should offer the option of nonfatal handling,
975 // like other validity errors, though that would cause major
976 // chaos in the DOM data structures. DOM is already spec'd
977 // to treat many of these as fatal, so this is consistent.
978 //
979 private void namespaceError (String description)
980 throws SAXParseException
981 {
982 SAXParseException err;
983
984 err = new SAXParseException (description, locator);
985 throw err;
986 }
987 }
988 }