001/*
002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
003 *
004 * This software is open source.
005 * See the bottom of this file for the licence.
006 *
007 * $Id: SAXContentHandler.java,v 1.42 2002/03/02 14:23:25 slehmann Exp $
008 */
009
010package org.dom4j.io;
011
012import java.util.ArrayList;
013import java.util.Iterator;
014import java.util.HashMap;
015import java.util.HashSet;
016import java.util.List;
017import java.util.Map;
018import java.util.Set;
019
020import org.dom4j.Attribute;
021import org.dom4j.Branch;
022import org.dom4j.CDATA;
023import org.dom4j.Comment;
024import org.dom4j.Document;
025import org.dom4j.DocumentType;
026import org.dom4j.DocumentFactory;
027import org.dom4j.Element;
028import org.dom4j.ElementHandler;
029import org.dom4j.Entity;
030import org.dom4j.Namespace;
031import org.dom4j.QName;
032import org.dom4j.ProcessingInstruction;
033import org.dom4j.DocumentException;
034
035import org.dom4j.dtd.AttributeDecl;
036import org.dom4j.dtd.ElementDecl;
037import org.dom4j.dtd.ExternalEntityDecl;
038import org.dom4j.dtd.InternalEntityDecl;
039
040import org.dom4j.tree.AbstractElement;
041import org.dom4j.tree.NamespaceStack;
042
043import org.xml.sax.Attributes;
044import org.xml.sax.DTDHandler;
045import org.xml.sax.EntityResolver;
046import org.xml.sax.InputSource;
047import org.xml.sax.SAXException;
048import org.xml.sax.SAXParseException;
049import org.xml.sax.ext.LexicalHandler;
050import org.xml.sax.ext.DeclHandler;
051import org.xml.sax.helpers.DefaultHandler;
052
053/** <p><code>SAXHandler</code> builds a DOM4J tree via SAX events.</p>
054  *
055  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
056  * @version $Revision: 1.42 $
057  */
058public class SAXContentHandler extends DefaultHandler implements LexicalHandler, DeclHandler, DTDHandler {
059
060    /** The factory used to create new <code>Document</code> instances */
061    private DocumentFactory documentFactory;
062
063    /** The document that is being built */
064    private Document document;
065
066    /** stack of <code>Element</code> objects */
067    private ElementStack elementStack;
068
069    /** stack of <code>Namespace</code> and <code>QName</code> objects */
070    private NamespaceStack namespaceStack;
071
072    /** the <code>ElementHandler</code> called as the elements are complete */
073    private ElementHandler elementHandler;
074
075    /** The name of the current entity */
076    private String entity;
077
078    /** Flag used to indicate that we are inside a DTD section */
079    private boolean insideDTDSection;
080
081    /** Flag used to indicate that we are inside a CDATA section */
082    private boolean insideCDATASection;
083
084    /** namespaces that are available for use */
085    private Map availableNamespaceMap = new HashMap();
086
087    /** declared namespaces that are not yet available for use */
088    private List declaredNamespaceList = new ArrayList();
089
090    /** internal DTD declarations */
091    private List internalDTDDeclarations;
092
093    /** external DTD declarations */
094    private List externalDTDDeclarations;
095
096    /** The number of namespaces that are declared in the current scope */
097    private int declaredNamespaceIndex;
098
099    /** The entity resolver */
100    private EntityResolver entityResolver;
101
102    private InputSource inputSource;
103
104    /** The current element we are on */
105    private Element currentElement;
106
107    /** Should internal DTD declarations be expanded into a List in the DTD */
108    private boolean includeInternalDTDDeclarations = false;
109
110    /** Should external DTD declarations be expanded into a List in the DTD */
111    private boolean includeExternalDTDDeclarations = false;
112
113    /** The number of levels deep we are inside a startEntity / endEntity call */
114    private int entityLevel;
115
116    /** Are we in an internal DTD subset? */
117    private boolean internalDTDsubset = false;
118
119    /** Whether adjacent text nodes should be merged */
120    private boolean mergeAdjacentText = false;
121
122    /** Have we added text to the buffer */
123    private boolean textInTextBuffer = false;
124
125    /** Buffer used to concatenate text together */
126    private StringBuffer textBuffer;
127
128    /** Holds value of property stripWhitespaceText. */
129    private boolean stripWhitespaceText = false;
130
131
132    public SAXContentHandler() {
133        this( DocumentFactory.getInstance() );
134    }
135
136    public SAXContentHandler(DocumentFactory documentFactory) {
137        this.documentFactory = documentFactory;
138        this.namespaceStack = new NamespaceStack(documentFactory);
139    }
140
141    public SAXContentHandler(DocumentFactory documentFactory, ElementHandler elementHandler) {
142        this.documentFactory = documentFactory;
143        this.elementHandler = elementHandler;
144        this.namespaceStack = new NamespaceStack(documentFactory);
145    }
146
147    public SAXContentHandler(DocumentFactory documentFactory, ElementHandler elementHandler, ElementStack elementStack) {
148        this.documentFactory = documentFactory;
149        this.elementHandler = elementHandler;
150        this.elementStack = elementStack;
151        this.namespaceStack = new NamespaceStack(documentFactory);
152    }
153
154    /** @return the document that has been or is being built
155      */
156    public Document getDocument() {
157        if ( document == null ) {
158            document = createDocument();
159        }
160        return document;
161    }
162
163    // ContentHandler interface
164    //-------------------------------------------------------------------------
165
166    public void processingInstruction(String target, String data) throws SAXException {
167        if ( mergeAdjacentText && textInTextBuffer ) {
168            completeCurrentTextNode();
169        }
170        if ( currentElement != null ) {
171            currentElement.addProcessingInstruction(target, data);
172        }
173        else {
174            document.addProcessingInstruction(target, data);
175        }
176    }
177
178    public void startPrefixMapping(String prefix, String uri) throws SAXException {
179        namespaceStack.push( prefix, uri );
180    }
181
182    public void endPrefixMapping(String prefix) throws SAXException {
183        namespaceStack.pop( prefix );
184        declaredNamespaceIndex = namespaceStack.size();
185    }
186
187    public void startDocument() throws SAXException {
188        document = createDocument();
189        currentElement = null;
190
191        if ( elementStack == null ) {
192            elementStack = createElementStack();
193        }
194        else {
195            elementStack.clear();
196        }
197        if ( (elementHandler != null) &&
198             (elementHandler instanceof DispatchHandler) ) {
199            elementStack.setDispatchHandler((DispatchHandler)elementHandler);
200        }
201
202        namespaceStack.clear();
203        declaredNamespaceIndex = 0;
204
205        if ( mergeAdjacentText && textBuffer == null ) {
206            textBuffer = new StringBuffer();
207        }
208        textInTextBuffer = false;
209    }
210
211    public void endDocument() throws SAXException {
212        namespaceStack.clear();
213        elementStack.clear();
214        currentElement = null;
215        textBuffer = null;
216    }
217
218    public void startElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes) throws SAXException {
219        if ( mergeAdjacentText && textInTextBuffer ) {
220            completeCurrentTextNode();
221        }
222
223        QName qName = namespaceStack.getQName(
224            namespaceURI, localName, qualifiedName
225        );
226
227        Branch branch = currentElement;
228        if ( branch == null ) {
229            branch = document;
230        }
231        Element element = branch.addElement(qName);
232
233        // add all declared namespaces
234        addDeclaredNamespaces(element);
235
236        // now lets add all attribute values
237        addAttributes( element, attributes );
238
239        elementStack.pushElement(element);
240        currentElement = element;
241
242
243        if ( elementHandler != null ) {
244            elementHandler.onStart(elementStack);
245        }
246    }
247
248    public void endElement(String namespaceURI, String localName, String qName) {
249        if ( mergeAdjacentText && textInTextBuffer ) {
250            completeCurrentTextNode();
251        }
252
253        if ( elementHandler != null && currentElement != null ) {
254            elementHandler.onEnd(elementStack);
255        }
256        elementStack.popElement();
257        currentElement = elementStack.peekElement();
258    }
259
260    public void characters(char[] ch, int start, int end) throws SAXException {
261        if ( end == 0 ) {
262            return;
263        }
264        if ( currentElement != null ) {
265            String text = new String(ch, start, end);
266            if (entity != null) {
267                if ( mergeAdjacentText && textInTextBuffer ) {
268                    completeCurrentTextNode();
269                }
270                currentElement.addEntity(entity, new String(ch, start, end));
271                entity = null;
272            }
273            else if (insideCDATASection) {
274                if ( mergeAdjacentText && textInTextBuffer ) {
275                    completeCurrentTextNode();
276                }
277                currentElement.addCDATA(new String(ch, start, end));
278            }
279            else {
280                if ( mergeAdjacentText ) {
281                    textBuffer.append(ch, start, end);
282                    textInTextBuffer = true;
283                }
284                else {
285                    currentElement.addText(new String(ch, start, end));
286                }
287            }
288        }
289    }
290
291    // ErrorHandler interface
292    //-------------------------------------------------------------------------
293
294    /** This method is called when a warning occurs during the parsing
295      * of the document.
296      * This method does nothing.
297      */
298    public void warning(SAXParseException exception) throws SAXException {
299        // ignore warnings by default
300    }
301
302    /** This method is called when an error is detected during parsing
303      * such as a validation error.
304      * This method rethrows the exception
305      */
306    public void error(SAXParseException exception) throws SAXException {
307        throw exception;
308    }
309
310    /** This method is called when a fatal error occurs during parsing.
311      * This method rethrows the exception
312      */
313    public void fatalError(SAXParseException exception) throws SAXException {
314        throw exception;
315    }
316
317    // LexicalHandler interface
318    //-------------------------------------------------------------------------
319
320    public void startDTD(String name, String publicId, String systemId) throws SAXException {
321        if (document != null) {
322            document.addDocType(name, publicId, systemId);
323        }
324        insideDTDSection = true;
325        internalDTDsubset = true;
326    }
327
328    public void endDTD() throws SAXException {
329        insideDTDSection = false;
330        if ( document != null ) {
331            DocumentType docType = document.getDocType();
332            if ( docType != null ) {
333                if ( internalDTDDeclarations != null ) {
334                    docType.setInternalDeclarations( internalDTDDeclarations );
335                }
336                if ( externalDTDDeclarations != null ) {
337                    docType.setExternalDeclarations( externalDTDDeclarations );
338                }
339            }
340        }
341        internalDTDDeclarations = null;
342        externalDTDDeclarations = null;
343    }
344
345    public void startEntity(String name) throws SAXException {
346        ++entityLevel;
347
348        // Ignore DTD references
349        entity = null;
350        if (! insideDTDSection ) {
351            if ( ! isIgnorableEntity(name) ) {
352                entity = name;
353            }
354        }
355
356        // internal DTD subsets can only appear outside of a
357        // startEntity/endEntity block
358        // see the startDTD method in
359        // http://dom4j.org/javadoc/org/xml/sax/ext/LexicalHandler.html
360        // or here:-
361        // http://dom4j.org/javadoc/org/xml/sax/ext/LexicalHandler.html#startDTD(java.lang.String, java.lang.String, java.lang.String)
362        internalDTDsubset = false;
363    }
364
365    public void endEntity(String name) throws SAXException {
366        --entityLevel;
367        entity = null;
368        if ( entityLevel == 0 ) {
369            internalDTDsubset = true;
370        }
371    }
372
373    public void startCDATA() throws SAXException {
374        insideCDATASection = true;
375    }
376
377    public void endCDATA() throws SAXException {
378        insideCDATASection = false;
379    }
380
381    public void comment(char[] ch, int start, int end) throws SAXException {
382        if ( mergeAdjacentText && textInTextBuffer ) {
383            completeCurrentTextNode();
384        }
385        String text = new String(ch, start, end);
386        if (!insideDTDSection && text.length() > 0) {
387            if ( currentElement != null ) {
388                currentElement.addComment(text);
389            }
390            else {
391                document.addComment(text);
392            }
393        }
394    }
395
396    // DeclHandler interface
397    //-------------------------------------------------------------------------
398
399    /**
400     * Report an element type declaration.
401     *
402     * <p>The content model will consist of the string "EMPTY", the
403     * string "ANY", or a parenthesised group, optionally followed
404     * by an occurrence indicator.  The model will be normalized so
405     * that all parameter entities are fully resolved and all whitespace
406     * is removed,and will include the enclosing parentheses.  Other
407     * normalization (such as removing redundant parentheses or
408     * simplifying occurrence indicators) is at the discretion of the
409     * parser.</p>
410     *
411     * @param name The element type name.
412     * @param model The content model as a normalized string.
413     * @exception SAXException The application may raise an exception.
414     */
415    public void elementDecl(String name, String model) throws SAXException {
416        if ( internalDTDsubset ) {
417            if ( includeInternalDTDDeclarations ) {
418                addDTDDeclaration( new ElementDecl( name, model ) );
419            }
420        }
421        else {
422            if ( includeExternalDTDDeclarations ) {
423                addExternalDTDDeclaration( new ElementDecl( name, model ) );
424            }
425        }
426    }
427
428    /**
429     * Report an attribute type declaration.
430     *
431     * <p>Only the effective (first) declaration for an attribute will
432     * be reported.  The type will be one of the strings "CDATA",
433     * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
434     * "ENTITIES", a parenthesized token group with
435     * the separator "|" and all whitespace removed, or the word
436     * "NOTATION" followed by a space followed by a parenthesized
437     * token group with all whitespace removed.</p>
438     *
439     * <p>Any parameter entities in the attribute value will be
440     * expanded, but general entities will not.</p>
441     *
442     * @param eName The name of the associated element.
443     * @param aName The name of the attribute.
444     * @param type A string representing the attribute type.
445     * @param valueDefault A string representing the attribute default
446     *       ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
447     *       none of these applies.
448     * @param value A string representing the attribute's default value,
449     *       or null if there is none.
450     * @exception SAXException The application may raise an exception.
451     */
452    public void attributeDecl(String eName,String aName,String type,String valueDefault,String value) throws SAXException {
453        if ( internalDTDsubset ) {
454            if ( includeInternalDTDDeclarations ) {
455                addDTDDeclaration( new AttributeDecl( eName, aName, type, valueDefault, value) );
456            }
457        }
458        else {
459            if ( includeExternalDTDDeclarations ) {
460                addExternalDTDDeclaration( new AttributeDecl( eName, aName, type, valueDefault, value) );
461            }
462        }
463    }
464
465    /**
466     * Report an internal entity declaration.
467     *
468     * <p>Only the effective (first) declaration for each entity
469     * will be reported.  All parameter entities in the value
470     * will be expanded, but general entities will not.</p>
471     *
472     * @param name The name of the entity.  If it is a parameter
473     *       entity, the name will begin with '%'.
474     * @param value The replacement text of the entity.
475     * @exception SAXException The application may raise an exception.
476     * @see #externalEntityDecl
477     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
478     */
479    public void internalEntityDecl(String name, String value) throws SAXException {
480        if ( internalDTDsubset ) {
481            if ( includeInternalDTDDeclarations ) {
482                addDTDDeclaration( new InternalEntityDecl( name, value ) );
483            }
484        }
485        else {
486            if ( includeExternalDTDDeclarations ) {
487                addExternalDTDDeclaration( new InternalEntityDecl( name, value ) );
488            }
489        }
490    }
491
492    /**
493     * Report a parsed external entity declaration.
494     *
495     * <p>Only the effective (first) declaration for each entity
496     * will be reported.</p>
497     *
498     * @param name The name of the entity.  If it is a parameter
499     *       entity, the name will begin with '%'.
500     * @param publicId The declared public identifier of the entity, or
501     *       null if none was declared.
502     * @param systemId The declared system identifier of the entity.
503     * @exception SAXException The application may raise an exception.
504     * @see #internalEntityDecl
505     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
506     */
507    public void externalEntityDecl(String name, String publicID, String systemID) throws SAXException {
508        if ( internalDTDsubset ) {
509            if ( includeInternalDTDDeclarations ) {
510                addDTDDeclaration( new ExternalEntityDecl( name, publicID, systemID ) );
511            }
512        }
513        else {
514            if ( includeExternalDTDDeclarations ) {
515                addExternalDTDDeclaration( new ExternalEntityDecl( name, publicID, systemID ) );
516            }
517        }
518    }
519
520
521    // DTDHandler interface
522    //-------------------------------------------------------------------------
523
524    /**
525     * Receive notification of a notation declaration event.
526     *
527     * <p>It is up to the application to record the notation for later
528     * reference, if necessary.</p>
529     *
530     * <p>At least one of publicId and systemId must be non-null.
531     * If a system identifier is present, and it is a URL, the SAX
532     * parser must resolve it fully before passing it to the
533     * application through this event.</p>
534     *
535     * <p>There is no guarantee that the notation declaration will be
536     * reported before any unparsed entities that use it.</p>
537     *
538     * @param name The notation name.
539     * @param publicId The notation's public identifier, or null if
540     *       none was given.
541     * @param systemId The notation's system identifier, or null if
542     *       none was given.
543     * @exception org.xml.sax.SAXException Any SAX exception, possibly
544     *           wrapping another exception.
545     * @see #unparsedEntityDecl
546     * @see org.xml.sax.AttributeList
547     */
548    public void notationDecl(String name,String publicId,String systemId) throws SAXException {
549        // #### not supported yet!
550    }
551
552    /**
553     * Receive notification of an unparsed entity declaration event.
554     *
555     * <p>Note that the notation name corresponds to a notation
556     * reported by the {@link #notationDecl notationDecl} event.
557     * It is up to the application to record the entity for later
558     * reference, if necessary.</p>
559     *
560     * <p>If the system identifier is a URL, the parser must resolve it
561     * fully before passing it to the application.</p>
562     *
563     * @exception org.xml.sax.SAXException Any SAX exception, possibly
564     *           wrapping another exception.
565     * @param name The unparsed entity's name.
566     * @param publicId The entity's public identifier, or null if none
567     *       was given.
568     * @param systemId The entity's system identifier.
569     * @param notation name The name of the associated notation.
570     * @see #notationDecl
571     * @see org.xml.sax.AttributeList
572     */
573    public void unparsedEntityDecl(String name,String publicId,String systemId,String notationName) throws SAXException {
574        // #### not supported yet!
575    }
576
577
578    // Properties
579    //-------------------------------------------------------------------------
580    public ElementStack getElementStack() {
581        return elementStack;
582    }
583
584    public void setElementStack(ElementStack elementStack) {
585        this.elementStack = elementStack;
586    }
587
588    public EntityResolver getEntityResolver() {
589        return entityResolver;
590    }
591
592    public void setEntityResolver(EntityResolver entityResolver) {
593        this.entityResolver = entityResolver;
594    }
595
596    public InputSource getInputSource() {
597        return inputSource;
598    }
599
600    public void setInputSource(InputSource inputSource) {
601        this.inputSource = inputSource;
602    }
603
604    /** @return whether internal DTD declarations should be expanded into the DocumentType
605      * object or not.
606      */
607    public boolean isIncludeInternalDTDDeclarations() {
608        return includeInternalDTDDeclarations;
609    }
610
611    /** Sets whether internal DTD declarations should be expanded into the DocumentType
612      * object or not.
613      *
614      * @param includeInternalDTDDeclarations whether or not DTD declarations should be expanded
615      * and included into the DocumentType object.
616      */
617    public void setIncludeInternalDTDDeclarations(boolean includeInternalDTDDeclarations) {
618        this.includeInternalDTDDeclarations = includeInternalDTDDeclarations;
619    }
620
621    /** @return whether external DTD declarations should be expanded into the DocumentType
622      * object or not.
623      */
624    public boolean isIncludeExternalDTDDeclarations() {
625        return includeExternalDTDDeclarations;
626    }
627
628    /** Sets whether DTD external declarations should be expanded into the DocumentType
629      * object or not.
630      *
631      * @param includeInternalDTDDeclarations whether or not DTD declarations should be expanded
632      * and included into the DocumentType object.
633      */
634    public void setIncludeExternalDTDDeclarations(boolean includeExternalDTDDeclarations) {
635        this.includeExternalDTDDeclarations = includeExternalDTDDeclarations;
636    }
637
638    /** Returns whether adjacent text nodes should be merged together.
639      * @return Value of property mergeAdjacentText.
640      */
641    public boolean isMergeAdjacentText() {
642        return mergeAdjacentText;
643    }
644
645    /** Sets whether or not adjacent text nodes should be merged
646      * together when parsing.
647      * @param mergeAdjacentText New value of property mergeAdjacentText.
648      */
649    public void setMergeAdjacentText(boolean mergeAdjacentText) {
650        this.mergeAdjacentText = mergeAdjacentText;
651    }
652
653
654    /** Sets whether whitespace between element start and end tags should be ignored
655      *
656      * @return Value of property stripWhitespaceText.
657      */
658    public boolean isStripWhitespaceText() {
659        return stripWhitespaceText;
660    }
661
662    /** Sets whether whitespace between element start and end tags should be ignored.
663      *
664      * @param stripWhitespaceText New value of property stripWhitespaceText.
665      */
666    public void setStripWhitespaceText(boolean stripWhitespaceText) {
667        this.stripWhitespaceText = stripWhitespaceText;
668    }
669
670    // Implementation methods
671    //-------------------------------------------------------------------------
672
673    /** If the current text buffer contains any text then create a new
674      * text node with it and add it to the current element
675      */
676    protected void completeCurrentTextNode() {
677        if ( stripWhitespaceText ) {
678            boolean whitespace = true;
679            for ( int i = 0, size = textBuffer.length(); i < size; i++ ) {
680                if ( ! Character.isWhitespace( textBuffer.charAt(i) ) ) {
681                    whitespace = false;
682                    break;
683                }
684            }
685            if ( ! whitespace ) {
686                currentElement.addText( textBuffer.toString() );
687            }
688        }
689        else {
690            currentElement.addText( textBuffer.toString() );
691        }
692        textBuffer.setLength(0);
693        textInTextBuffer = false;
694    }
695
696    /** @return the current document
697      */
698    protected Document createDocument() {
699        Document document = documentFactory.createDocument();
700
701        // set the EntityResolver
702        document.setEntityResolver(entityResolver);
703        if ( inputSource != null ) {
704            document.setName( inputSource.getSystemId() );
705        }
706
707        return document;
708    }
709
710    /** a Strategy Method to determine if a given entity name is ignorable
711      */
712    protected boolean isIgnorableEntity(String name) {
713        return "amp".equals( name )
714            || "apos".equals( name )
715            || "gt".equals( name )
716            || "lt".equals( name )
717            || "quot".equals( name );
718    }
719
720
721    /** Add all namespaces declared before the startElement() SAX event
722      * to the current element so that they are available to child elements
723      * and attributes
724      */
725    protected void addDeclaredNamespaces(Element element) {
726        Namespace elementNamespace = element.getNamespace();
727        for ( int size = namespaceStack.size(); declaredNamespaceIndex < size; declaredNamespaceIndex++ ) {
728            Namespace namespace = namespaceStack.getNamespace(declaredNamespaceIndex);
729            if ( namespace != elementNamespace ) {
730                element.add( namespace );
731            }
732        }
733    }
734
735    /** Add all the attributes to the given elements
736      */
737    protected void addAttributes( Element element, Attributes attributes ) {
738        // XXXX: as an optimisation, we could deduce this value from the current
739        // SAX parser settings, the SAX namespaces-prefixes feature
740
741        boolean noNamespaceAttributes = false;
742        if ( element instanceof AbstractElement ) {
743            // optimised method
744            AbstractElement baseElement = (AbstractElement) element;
745            baseElement.setAttributes( attributes, namespaceStack, noNamespaceAttributes );
746        }
747        else {
748            int size = attributes.getLength();
749            for ( int i = 0; i < size; i++ ) {
750                String attributeQualifiedName = attributes.getQName(i);
751                if ( noNamespaceAttributes || ! attributeQualifiedName.startsWith( "xmlns" ) ) {
752                    String attributeURI = attributes.getURI(i);
753                    String attributeLocalName = attributes.getLocalName(i);
754                    String attributeValue = attributes.getValue(i);
755
756                    QName attributeQName = namespaceStack.getAttributeQName(
757                        attributeURI, attributeLocalName, attributeQualifiedName
758                    );
759                    element.addAttribute(attributeQName, attributeValue);
760                }
761            }
762        }
763    }
764
765
766    /** Adds an internal DTD declaration to the list of declarations */
767    protected void addDTDDeclaration(Object declaration) {
768        if ( internalDTDDeclarations == null ) {
769            internalDTDDeclarations = new ArrayList();
770        }
771        internalDTDDeclarations.add( declaration );
772    }
773
774    /** Adds an external DTD declaration to the list of declarations */
775    protected void addExternalDTDDeclaration(Object declaration) {
776        if ( externalDTDDeclarations == null ) {
777            externalDTDDeclarations = new ArrayList();
778        }
779        externalDTDDeclarations.add( declaration );
780    }
781
782    protected ElementStack createElementStack() {
783        return new ElementStack();
784    }
785}
786
787
788
789
790/*
791 * Redistribution and use of this software and associated documentation
792 * ("Software"), with or without modification, are permitted provided
793 * that the following conditions are met:
794 *
795 * 1. Redistributions of source code must retain copyright
796 *    statements and notices.  Redistributions must also contain a
797 *    copy of this document.
798 *
799 * 2. Redistributions in binary form must reproduce the
800 *    above copyright notice, this list of conditions and the
801 *    following disclaimer in the documentation and/or other
802 *    materials provided with the distribution.
803 *
804 * 3. The name "DOM4J" must not be used to endorse or promote
805 *    products derived from this Software without prior written
806 *    permission of MetaStuff, Ltd.  For written permission,
807 *    please contact dom4j-info@metastuff.com.
808 *
809 * 4. Products derived from this Software may not be called "DOM4J"
810 *    nor may "DOM4J" appear in their names without prior written
811 *    permission of MetaStuff, Ltd. DOM4J is a registered
812 *    trademark of MetaStuff, Ltd.
813 *
814 * 5. Due credit should be given to the DOM4J Project
815 *    (http://dom4j.org/).
816 *
817 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
818 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
819 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
820 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
821 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
822 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
823 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
824 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
825 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
826 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
827 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
828 * OF THE POSSIBILITY OF SUCH DAMAGE.
829 *
830 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
831 *
832 * $Id: SAXContentHandler.java,v 1.42 2002/03/02 14:23:25 slehmann Exp $
833 */