View Javadoc

1   /*
2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
3    *
4    * This software is open source.
5    * See the bottom of this file for the licence.
6    */
7   
8   package org.dom4j.io;
9   
10  import java.lang.reflect.Method;
11  import java.util.ArrayList;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.Map;
15  
16  import org.dom4j.Branch;
17  import org.dom4j.Document;
18  import org.dom4j.DocumentFactory;
19  import org.dom4j.DocumentType;
20  import org.dom4j.Element;
21  import org.dom4j.ElementHandler;
22  import org.dom4j.Namespace;
23  import org.dom4j.QName;
24  import org.dom4j.dtd.AttributeDecl;
25  import org.dom4j.dtd.ElementDecl;
26  import org.dom4j.dtd.ExternalEntityDecl;
27  import org.dom4j.dtd.InternalEntityDecl;
28  import org.dom4j.tree.AbstractElement;
29  import org.dom4j.tree.NamespaceStack;
30  
31  import org.xml.sax.Attributes;
32  import org.xml.sax.DTDHandler;
33  import org.xml.sax.EntityResolver;
34  import org.xml.sax.InputSource;
35  import org.xml.sax.Locator;
36  import org.xml.sax.SAXException;
37  import org.xml.sax.SAXParseException;
38  import org.xml.sax.ext.DeclHandler;
39  import org.xml.sax.ext.LexicalHandler;
40  import org.xml.sax.helpers.DefaultHandler;
41  
42  /***
43   * <p>
44   * <code>SAXContentHandler</code> builds a dom4j tree via SAX events.
45   * </p>
46   * 
47   * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
48   * @version $Revision: 1.61 $
49   */
50  public class SAXContentHandler extends DefaultHandler implements
51          LexicalHandler, DeclHandler, DTDHandler {
52      /*** The factory used to create new <code>Document</code> instances */
53      private DocumentFactory documentFactory;
54  
55      /*** The document that is being built */
56      private Document document;
57  
58      /*** stack of <code>Element</code> objects */
59      private ElementStack elementStack;
60  
61      /*** stack of <code>Namespace</code> and <code>QName</code> objects */
62      private NamespaceStack namespaceStack;
63  
64      /*** the <code>ElementHandler</code> called as the elements are complete */
65      private ElementHandler elementHandler;
66  
67      /*** the Locator */
68      private Locator locator;
69  
70      /*** The name of the current entity */
71      private String entity;
72  
73      /*** Flag used to indicate that we are inside a DTD section */
74      private boolean insideDTDSection;
75  
76      /*** Flag used to indicate that we are inside a CDATA section */
77      private boolean insideCDATASection;
78  
79      /***
80       * buffer to hold contents of cdata section across multiple characters
81       * events
82       */
83      private StringBuffer cdataText;
84  
85      /*** namespaces that are available for use */
86      private Map availableNamespaceMap = new HashMap();
87  
88      /*** declared namespaces that are not yet available for use */
89      private List declaredNamespaceList = new ArrayList();
90  
91      /*** internal DTD declarations */
92      private List internalDTDDeclarations;
93  
94      /*** external DTD declarations */
95      private List externalDTDDeclarations;
96  
97      /*** The number of namespaces that are declared in the current scope */
98      private int declaredNamespaceIndex;
99  
100     /*** The entity resolver */
101     private EntityResolver entityResolver;
102 
103     private InputSource inputSource;
104 
105     /*** The current element we are on */
106     private Element currentElement;
107 
108     /*** Should internal DTD declarations be expanded into a List in the DTD */
109     private boolean includeInternalDTDDeclarations = false;
110 
111     /*** Should external DTD declarations be expanded into a List in the DTD */
112     private boolean includeExternalDTDDeclarations = false;
113 
114     /*** The number of levels deep we are inside a startEntity/endEntity call */
115     private int entityLevel;
116 
117     /*** Are we in an internal DTD subset? */
118     private boolean internalDTDsubset = false;
119 
120     /*** Whether adjacent text nodes should be merged */
121     private boolean mergeAdjacentText = false;
122 
123     /*** Have we added text to the buffer */
124     private boolean textInTextBuffer = false;
125 
126     /*** Should we ignore comments */
127     private boolean ignoreComments = false;
128 
129     /*** Buffer used to concatenate text together */
130     private StringBuffer textBuffer;
131 
132     /*** Holds value of property stripWhitespaceText. */
133     private boolean stripWhitespaceText = false;
134 
135     public SAXContentHandler() {
136         this(DocumentFactory.getInstance());
137     }
138 
139     public SAXContentHandler(DocumentFactory documentFactory) {
140         this(documentFactory, null);
141     }
142 
143     public SAXContentHandler(DocumentFactory documentFactory,
144             ElementHandler elementHandler) {
145         this(documentFactory, elementHandler, null);
146         this.elementStack = createElementStack();
147     }
148 
149     public SAXContentHandler(DocumentFactory documentFactory,
150             ElementHandler elementHandler, ElementStack elementStack) {
151         this.documentFactory = documentFactory;
152         this.elementHandler = elementHandler;
153         this.elementStack = elementStack;
154         this.namespaceStack = new NamespaceStack(documentFactory);
155     }
156 
157     /***
158      * DOCUMENT ME!
159      * 
160      * @return the document that has been or is being built
161      */
162     public Document getDocument() {
163         if (document == null) {
164             document = createDocument();
165         }
166 
167         return document;
168     }
169 
170     // ContentHandler interface
171     // -------------------------------------------------------------------------
172     public void setDocumentLocator(Locator documentLocator) {
173         this.locator = documentLocator;
174     }
175 
176     public void processingInstruction(String target, String data)
177             throws SAXException {
178         if (mergeAdjacentText && textInTextBuffer) {
179             completeCurrentTextNode();
180         }
181 
182         if (currentElement != null) {
183             currentElement.addProcessingInstruction(target, data);
184         } else {
185             getDocument().addProcessingInstruction(target, data);
186         }
187     }
188 
189     public void startPrefixMapping(String prefix, String uri)
190             throws SAXException {
191         namespaceStack.push(prefix, uri);
192     }
193 
194     public void endPrefixMapping(String prefix) throws SAXException {
195         namespaceStack.pop(prefix);
196         declaredNamespaceIndex = namespaceStack.size();
197     }
198 
199     public void startDocument() throws SAXException {
200         // document = createDocument();
201         document = null;
202         currentElement = null;
203 
204         elementStack.clear();
205 
206         if ((elementHandler != null)
207                 && (elementHandler instanceof DispatchHandler)) {
208             elementStack.setDispatchHandler((DispatchHandler) elementHandler);
209         }
210 
211         namespaceStack.clear();
212         declaredNamespaceIndex = 0;
213 
214         if (mergeAdjacentText && (textBuffer == null)) {
215             textBuffer = new StringBuffer();
216         }
217 
218         textInTextBuffer = false;
219     }
220 
221     public void endDocument() throws SAXException {
222         namespaceStack.clear();
223         elementStack.clear();
224         currentElement = null;
225         textBuffer = null;
226     }
227 
228     public void startElement(String namespaceURI, String localName,
229             String qualifiedName, Attributes attributes) throws SAXException {
230         if (mergeAdjacentText && textInTextBuffer) {
231             completeCurrentTextNode();
232         }
233 
234         QName qName = namespaceStack.getQName(namespaceURI, localName,
235                 qualifiedName);
236 
237         Branch branch = currentElement;
238 
239         if (branch == null) {
240             branch = getDocument();
241         }
242 
243         Element element = branch.addElement(qName);
244 
245         // add all declared namespaces
246         addDeclaredNamespaces(element);
247 
248         // now lets add all attribute values
249         addAttributes(element, attributes);
250 
251         elementStack.pushElement(element);
252         currentElement = element;
253 
254         entity = null; // fixes bug527062
255 
256         if (elementHandler != null) {
257             elementHandler.onStart(elementStack);
258         }
259     }
260 
261     public void endElement(String namespaceURI, String localName, String qName)
262             throws SAXException {
263         if (mergeAdjacentText && textInTextBuffer) {
264             completeCurrentTextNode();
265         }
266 
267         if ((elementHandler != null) && (currentElement != null)) {
268             elementHandler.onEnd(elementStack);
269         }
270 
271         elementStack.popElement();
272         currentElement = elementStack.peekElement();
273     }
274 
275     public void characters(char[] ch, int start, int end) throws SAXException {
276         if (end == 0) {
277             return;
278         }
279 
280         if (currentElement != null) {
281             if (entity != null) {
282                 if (mergeAdjacentText && textInTextBuffer) {
283                     completeCurrentTextNode();
284                 }
285 
286                 currentElement.addEntity(entity, new String(ch, start, end));
287                 entity = null;
288             } else if (insideCDATASection) {
289                 if (mergeAdjacentText && textInTextBuffer) {
290                     completeCurrentTextNode();
291                 }
292 
293                 cdataText.append(new String(ch, start, end));
294             } else {
295                 if (mergeAdjacentText) {
296                     textBuffer.append(ch, start, end);
297                     textInTextBuffer = true;
298                 } else {
299                     currentElement.addText(new String(ch, start, end));
300                 }
301             }
302         }
303     }
304 
305     // ErrorHandler interface
306     // -------------------------------------------------------------------------
307 
308     /***
309      * This method is called when a warning occurs during the parsing of the
310      * document. This method does nothing.
311      * 
312      * @param exception
313      *            DOCUMENT ME!
314      * 
315      * @throws SAXException
316      *             DOCUMENT ME!
317      */
318     public void warning(SAXParseException exception) throws SAXException {
319         // ignore warnings by default
320     }
321 
322     /***
323      * This method is called when an error is detected during parsing such as a
324      * validation error. This method rethrows the exception
325      * 
326      * @param exception
327      *            DOCUMENT ME!
328      * 
329      * @throws SAXException
330      *             DOCUMENT ME!
331      */
332     public void error(SAXParseException exception) throws SAXException {
333         throw exception;
334     }
335 
336     /***
337      * This method is called when a fatal error occurs during parsing. This
338      * method rethrows the exception
339      * 
340      * @param exception
341      *            DOCUMENT ME!
342      * 
343      * @throws SAXException
344      *             DOCUMENT ME!
345      */
346     public void fatalError(SAXParseException exception) throws SAXException {
347         throw exception;
348     }
349 
350     // LexicalHandler interface
351     // -------------------------------------------------------------------------
352     public void startDTD(String name, String publicId, String systemId)
353             throws SAXException {
354         getDocument().addDocType(name, publicId, systemId);
355         insideDTDSection = true;
356         internalDTDsubset = true;
357     }
358 
359     public void endDTD() throws SAXException {
360         insideDTDSection = false;
361 
362         DocumentType docType = getDocument().getDocType();
363 
364         if (docType != null) {
365             if (internalDTDDeclarations != null) {
366                 docType.setInternalDeclarations(internalDTDDeclarations);
367             }
368 
369             if (externalDTDDeclarations != null) {
370                 docType.setExternalDeclarations(externalDTDDeclarations);
371             }
372         }
373 
374         internalDTDDeclarations = null;
375         externalDTDDeclarations = null;
376     }
377 
378     public void startEntity(String name) throws SAXException {
379         ++entityLevel;
380 
381         // Ignore DTD references
382         entity = null;
383 
384         if (!insideDTDSection) {
385             if (!isIgnorableEntity(name)) {
386                 entity = name;
387             }
388         }
389 
390         // internal DTD subsets can only appear outside of a
391         // startEntity/endEntity block
392         // see the startDTD method in
393         // http://dom4j.org/javadoc/org/xml/sax/ext/LexicalHandler.html
394         internalDTDsubset = false;
395     }
396 
397     public void endEntity(String name) throws SAXException {
398         --entityLevel;
399         entity = null;
400 
401         if (entityLevel == 0) {
402             internalDTDsubset = true;
403         }
404     }
405 
406     public void startCDATA() throws SAXException {
407         insideCDATASection = true;
408         cdataText = new StringBuffer();
409     }
410 
411     public void endCDATA() throws SAXException {
412         insideCDATASection = false;
413         currentElement.addCDATA(cdataText.toString());
414     }
415 
416     public void comment(char[] ch, int start, int end) throws SAXException {
417         if (!ignoreComments) {
418             if (mergeAdjacentText && textInTextBuffer) {
419                 completeCurrentTextNode();
420             }
421 
422             String text = new String(ch, start, end);
423 
424             if (!insideDTDSection && (text.length() > 0)) {
425                 if (currentElement != null) {
426                     currentElement.addComment(text);
427                 } else {
428                     getDocument().addComment(text);
429                 }
430             }
431         }
432     }
433 
434     // DeclHandler interface
435     // -------------------------------------------------------------------------
436 
437     /***
438      * Report an element type declaration.
439      * 
440      * <p>
441      * The content model will consist of the string "EMPTY", the string "ANY",
442      * or a parenthesised group, optionally followed by an occurrence indicator.
443      * The model will be normalized so that all parameter entities are fully
444      * resolved and all whitespace is removed,and will include the enclosing
445      * parentheses. Other normalization (such as removing redundant parentheses
446      * or simplifying occurrence indicators) is at the discretion of the parser.
447      * </p>
448      * 
449      * @param name
450      *            The element type name.
451      * @param model
452      *            The content model as a normalized string.
453      * 
454      * @exception SAXException
455      *                The application may raise an exception.
456      */
457     public void elementDecl(String name, String model) throws SAXException {
458         if (internalDTDsubset) {
459             if (includeInternalDTDDeclarations) {
460                 addDTDDeclaration(new ElementDecl(name, model));
461             }
462         } else {
463             if (includeExternalDTDDeclarations) {
464                 addExternalDTDDeclaration(new ElementDecl(name, model));
465             }
466         }
467     }
468 
469     /***
470      * Report an attribute type declaration.
471      * 
472      * <p>
473      * Only the effective (first) declaration for an attribute will be reported.
474      * The type will be one of the strings "CDATA", "ID", "IDREF", "IDREFS",
475      * "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", a parenthesized token group
476      * with the separator "|" and all whitespace removed, or the word "NOTATION"
477      * followed by a space followed by a parenthesized token group with all
478      * whitespace removed.
479      * </p>
480      * 
481      * <p>
482      * Any parameter entities in the attribute value will be expanded, but
483      * general entities will not.
484      * </p>
485      * 
486      * @param eName
487      *            The name of the associated element.
488      * @param aName
489      *            The name of the attribute.
490      * @param type
491      *            A string representing the attribute type.
492      * @param valueDefault
493      *            A string representing the attribute default ("#IMPLIED",
494      *            "#REQUIRED", or "#FIXED") or null if none of these applies.
495      * @param val
496      *            A string representing the attribute's default value, or null
497      *            if there is none.
498      * 
499      * @exception SAXException
500      *                The application may raise an exception.
501      */
502     public void attributeDecl(String eName, String aName, String type,
503             String valueDefault, String val) throws SAXException {
504         if (internalDTDsubset) {
505             if (includeInternalDTDDeclarations) {
506                 addDTDDeclaration(new AttributeDecl(eName, aName, type,
507                         valueDefault, val));
508             }
509         } else {
510             if (includeExternalDTDDeclarations) {
511                 addExternalDTDDeclaration(new AttributeDecl(eName, aName, type,
512                         valueDefault, val));
513             }
514         }
515     }
516 
517     /***
518      * Report an internal entity declaration.
519      * 
520      * <p>
521      * Only the effective (first) declaration for each entity will be reported.
522      * All parameter entities in the value will be expanded, but general
523      * entities will not.
524      * </p>
525      * 
526      * @param name
527      *            The name of the entity. If it is a parameter entity, the name
528      *            will begin with '%'.
529      * @param value
530      *            The replacement text of the entity.
531      * 
532      * @exception SAXException
533      *                The application may raise an exception.
534      * 
535      * @see #externalEntityDecl
536      * @see org.xml.sax.DTDHandler#unparsedEntityDecl
537      */
538     public void internalEntityDecl(String name, String value)
539             throws SAXException {
540         if (internalDTDsubset) {
541             if (includeInternalDTDDeclarations) {
542                 addDTDDeclaration(new InternalEntityDecl(name, value));
543             }
544         } else {
545             if (includeExternalDTDDeclarations) {
546                 addExternalDTDDeclaration(new InternalEntityDecl(name, value));
547             }
548         }
549     }
550 
551     /***
552      * Report a parsed external entity declaration.
553      * 
554      * <p>
555      * Only the effective (first) declaration for each entity will be reported.
556      * </p>
557      * 
558      * @param name
559      *            The name of the entity. If it is a parameter entity, the name
560      *            will begin with '%'.
561      * @param publicId
562      *            The declared public identifier of the entity, or null if none
563      *            was declared.
564      * @param sysId
565      *            The declared system identifier of the entity.
566      * 
567      * @exception SAXException
568      *                The application may raise an exception.
569      * 
570      * @see #internalEntityDecl
571      * @see org.xml.sax.DTDHandler#unparsedEntityDecl
572      */
573     public void externalEntityDecl(String name, String publicId, String sysId)
574             throws SAXException {
575         ExternalEntityDecl declaration = new ExternalEntityDecl(name, publicId,
576                 sysId);
577 
578         if (internalDTDsubset) {
579             if (includeInternalDTDDeclarations) {
580                 addDTDDeclaration(declaration);
581             }
582         } else {
583             if (includeExternalDTDDeclarations) {
584                 addExternalDTDDeclaration(declaration);
585             }
586         }
587     }
588 
589     // DTDHandler interface
590     // -------------------------------------------------------------------------
591 
592     /***
593      * Receive notification of a notation declaration event.
594      * 
595      * <p>
596      * It is up to the application to record the notation for later reference,
597      * if necessary.
598      * </p>
599      * 
600      * <p>
601      * At least one of publicId and systemId must be non-null. If a system
602      * identifier is present, and it is a URL, the SAX parser must resolve it
603      * fully before passing it to the application through this event.
604      * </p>
605      * 
606      * <p>
607      * There is no guarantee that the notation declaration will be reported
608      * before any unparsed entities that use it.
609      * </p>
610      * 
611      * @param name
612      *            The notation name.
613      * @param publicId
614      *            The notation's public identifier, or null if none was given.
615      * @param systemId
616      *            The notation's system identifier, or null if none was given.
617      * 
618      * @exception SAXException
619      *                Any SAX exception, possibly wrapping another exception.
620      * 
621      * @see #unparsedEntityDecl
622      * @see org.xml.sax.AttributeList
623      */
624     public void notationDecl(String name, String publicId, String systemId)
625             throws SAXException {
626         // #### not supported yet!
627     }
628 
629     /***
630      * Receive notification of an unparsed entity declaration event.
631      * 
632      * <p>
633      * Note that the notation name corresponds to a notation reported by the
634      * {@link #notationDecl notationDecl}event. It is up to the application to
635      * record the entity for later reference, if necessary.
636      * </p>
637      * 
638      * <p>
639      * If the system identifier is a URL, the parser must resolve it fully
640      * before passing it to the application.
641      * </p>
642      * 
643      * @param name
644      *            The unparsed entity's name.
645      * @param publicId
646      *            The entity's public identifier, or null if none was given.
647      * @param systemId
648      *            The entity's system identifier.
649      * @param notationName
650      *            The name of the associated notation.
651      * 
652      * @exception SAXException
653      *                Any SAX exception, possibly wrapping another exception.
654      * 
655      * @see #notationDecl
656      * @see org.xml.sax.AttributeList
657      */
658     public void unparsedEntityDecl(String name, String publicId,
659             String systemId, String notationName) throws SAXException {
660         // #### not supported yet!
661     }
662 
663     // Properties
664     // -------------------------------------------------------------------------
665     public ElementStack getElementStack() {
666         return elementStack;
667     }
668 
669     public void setElementStack(ElementStack elementStack) {
670         this.elementStack = elementStack;
671     }
672 
673     public EntityResolver getEntityResolver() {
674         return entityResolver;
675     }
676 
677     public void setEntityResolver(EntityResolver entityResolver) {
678         this.entityResolver = entityResolver;
679     }
680 
681     public InputSource getInputSource() {
682         return inputSource;
683     }
684 
685     public void setInputSource(InputSource inputSource) {
686         this.inputSource = inputSource;
687     }
688 
689     /***
690      * DOCUMENT ME!
691      * 
692      * @return whether internal DTD declarations should be expanded into the
693      *         DocumentType object or not.
694      */
695     public boolean isIncludeInternalDTDDeclarations() {
696         return includeInternalDTDDeclarations;
697     }
698 
699     /***
700      * Sets whether internal DTD declarations should be expanded into the
701      * DocumentType object or not.
702      * 
703      * @param include
704      *            whether or not DTD declarations should be expanded and
705      *            included into the DocumentType object.
706      */
707     public void setIncludeInternalDTDDeclarations(boolean include) {
708         this.includeInternalDTDDeclarations = include;
709     }
710 
711     /***
712      * DOCUMENT ME!
713      * 
714      * @return whether external DTD declarations should be expanded into the
715      *         DocumentType object or not.
716      */
717     public boolean isIncludeExternalDTDDeclarations() {
718         return includeExternalDTDDeclarations;
719     }
720 
721     /***
722      * Sets whether DTD external declarations should be expanded into the
723      * DocumentType object or not.
724      * 
725      * @param include
726      *            whether or not DTD declarations should be expanded and
727      *            included into the DocumentType object.
728      */
729     public void setIncludeExternalDTDDeclarations(boolean include) {
730         this.includeExternalDTDDeclarations = include;
731     }
732 
733     /***
734      * Returns whether adjacent text nodes should be merged together.
735      * 
736      * @return Value of property mergeAdjacentText.
737      */
738     public boolean isMergeAdjacentText() {
739         return mergeAdjacentText;
740     }
741 
742     /***
743      * Sets whether or not adjacent text nodes should be merged together when
744      * parsing.
745      * 
746      * @param mergeAdjacentText
747      *            New value of property mergeAdjacentText.
748      */
749     public void setMergeAdjacentText(boolean mergeAdjacentText) {
750         this.mergeAdjacentText = mergeAdjacentText;
751     }
752 
753     /***
754      * Sets whether whitespace between element start and end tags should be
755      * ignored
756      * 
757      * @return Value of property stripWhitespaceText.
758      */
759     public boolean isStripWhitespaceText() {
760         return stripWhitespaceText;
761     }
762 
763     /***
764      * Sets whether whitespace between element start and end tags should be
765      * ignored.
766      * 
767      * @param stripWhitespaceText
768      *            New value of property stripWhitespaceText.
769      */
770     public void setStripWhitespaceText(boolean stripWhitespaceText) {
771         this.stripWhitespaceText = stripWhitespaceText;
772     }
773 
774     /***
775      * Returns whether we should ignore comments or not.
776      * 
777      * @return boolean
778      */
779     public boolean isIgnoreComments() {
780         return ignoreComments;
781     }
782 
783     /***
784      * Sets whether we should ignore comments or not.
785      * 
786      * @param ignoreComments
787      *            whether we should ignore comments or not.
788      */
789     public void setIgnoreComments(boolean ignoreComments) {
790         this.ignoreComments = ignoreComments;
791     }
792 
793     // Implementation methods
794     // -------------------------------------------------------------------------
795 
796     /***
797      * If the current text buffer contains any text then create a new text node
798      * with it and add it to the current element
799      */
800     protected void completeCurrentTextNode() {
801         if (stripWhitespaceText) {
802             boolean whitespace = true;
803 
804             for (int i = 0, size = textBuffer.length(); i < size; i++) {
805                 if (!Character.isWhitespace(textBuffer.charAt(i))) {
806                     whitespace = false;
807 
808                     break;
809                 }
810             }
811 
812             if (!whitespace) {
813                 currentElement.addText(textBuffer.toString());
814             }
815         } else {
816             currentElement.addText(textBuffer.toString());
817         }
818 
819         textBuffer.setLength(0);
820         textInTextBuffer = false;
821     }
822 
823     /***
824      * DOCUMENT ME!
825      * 
826      * @return the current document
827      */
828     protected Document createDocument() {
829         String encoding = getEncoding();
830         Document doc = documentFactory.createDocument(encoding);
831 
832         // set the EntityResolver
833         doc.setEntityResolver(entityResolver);
834 
835         if (inputSource != null) {
836             doc.setName(inputSource.getSystemId());
837         }
838 
839         return doc;
840     }
841 
842     private String getEncoding() {
843         if (locator == null) {
844             return null;
845         }
846 
847         // use reflection to avoid dependency on Locator2
848         // or other locator implemenations.
849         try {
850             Method m = locator.getClass().getMethod("getEncoding",
851                     new Class[] {});
852 
853             if (m != null) {
854                 return (String) m.invoke(locator, null);
855             }
856         } catch (Exception e) {
857             // do nothing
858         }
859 
860         // couldn't determine encoding, returning null...
861         return null;
862     }
863 
864     /***
865      * a Strategy Method to determine if a given entity name is ignorable
866      * 
867      * @param name
868      *            DOCUMENT ME!
869      * 
870      * @return DOCUMENT ME!
871      */
872     protected boolean isIgnorableEntity(String name) {
873         return "amp".equals(name) || "apos".equals(name) || "gt".equals(name)
874                 || "lt".equals(name) || "quot".equals(name);
875     }
876 
877     /***
878      * Add all namespaces declared before the startElement() SAX event to the
879      * current element so that they are available to child elements and
880      * attributes
881      * 
882      * @param element
883      *            DOCUMENT ME!
884      */
885     protected void addDeclaredNamespaces(Element element) {
886         Namespace elementNamespace = element.getNamespace();
887 
888         for (int size = namespaceStack.size(); declaredNamespaceIndex < size; 
889                 declaredNamespaceIndex++) {
890             Namespace namespace = namespaceStack
891                     .getNamespace(declaredNamespaceIndex);
892 
893             // if ( namespace != elementNamespace ) {
894             element.add(namespace);
895 
896             // }
897         }
898     }
899 
900     /***
901      * Add all the attributes to the given elements
902      * 
903      * @param element
904      *            DOCUMENT ME!
905      * @param attributes
906      *            DOCUMENT ME!
907      */
908     protected void addAttributes(Element element, Attributes attributes) {
909         // XXXX: as an optimisation, we could deduce this value from the current
910         // SAX parser settings, the SAX namespaces-prefixes feature
911         boolean noNamespaceAttributes = false;
912 
913         if (element instanceof AbstractElement) {
914             // optimised method
915             AbstractElement baseElement = (AbstractElement) element;
916             baseElement.setAttributes(attributes, namespaceStack,
917                     noNamespaceAttributes);
918         } else {
919             int size = attributes.getLength();
920 
921             for (int i = 0; i < size; i++) {
922                 String attributeQName = attributes.getQName(i);
923 
924                 if (noNamespaceAttributes
925                         || !attributeQName.startsWith("xmlns")) {
926                     String attributeURI = attributes.getURI(i);
927                     String attributeLocalName = attributes.getLocalName(i);
928                     String attributeValue = attributes.getValue(i);
929 
930                     QName qName = namespaceStack.getAttributeQName(
931                             attributeURI, attributeLocalName, attributeQName);
932                     element.addAttribute(qName, attributeValue);
933                 }
934             }
935         }
936     }
937 
938     /***
939      * Adds an internal DTD declaration to the list of declarations
940      * 
941      * @param declaration
942      *            DOCUMENT ME!
943      */
944     protected void addDTDDeclaration(Object declaration) {
945         if (internalDTDDeclarations == null) {
946             internalDTDDeclarations = new ArrayList();
947         }
948 
949         internalDTDDeclarations.add(declaration);
950     }
951 
952     /***
953      * Adds an external DTD declaration to the list of declarations
954      * 
955      * @param declaration
956      *            DOCUMENT ME!
957      */
958     protected void addExternalDTDDeclaration(Object declaration) {
959         if (externalDTDDeclarations == null) {
960             externalDTDDeclarations = new ArrayList();
961         }
962 
963         externalDTDDeclarations.add(declaration);
964     }
965 
966     protected ElementStack createElementStack() {
967         return new ElementStack();
968     }
969 }
970 
971 /*
972  * Redistribution and use of this software and associated documentation
973  * ("Software"), with or without modification, are permitted provided that the
974  * following conditions are met:
975  * 
976  * 1. Redistributions of source code must retain copyright statements and
977  * notices. Redistributions must also contain a copy of this document.
978  * 
979  * 2. Redistributions in binary form must reproduce the above copyright notice,
980  * this list of conditions and the following disclaimer in the documentation
981  * and/or other materials provided with the distribution.
982  * 
983  * 3. The name "DOM4J" must not be used to endorse or promote products derived
984  * from this Software without prior written permission of MetaStuff, Ltd. For
985  * written permission, please contact dom4j-info@metastuff.com.
986  * 
987  * 4. Products derived from this Software may not be called "DOM4J" nor may
988  * "DOM4J" appear in their names without prior written permission of MetaStuff,
989  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
990  * 
991  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
992  * 
993  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
994  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
995  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
996  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
997  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
998  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
999  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
1000  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1001  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1002  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1003  * POSSIBILITY OF SUCH DAMAGE.
1004  * 
1005  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
1006  */