Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/jdom/input/SAXHandler.java


1   /*--
2   
3    $Id: SAXHandler.java,v 1.68 2004/08/31 06:14:05 jhunter Exp $
4   
5    Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
6    All rights reserved.
7   
8    Redistribution and use in source and binary forms, with or without
9    modification, are permitted provided that the following conditions
10   are met:
11  
12   1. Redistributions of source code must retain the above copyright
13      notice, this list of conditions, and the following disclaimer.
14  
15   2. Redistributions in binary form must reproduce the above copyright
16      notice, this list of conditions, and the disclaimer that follows
17      these conditions in the documentation and/or other materials
18      provided with the distribution.
19  
20   3. The name "JDOM" must not be used to endorse or promote products
21      derived from this software without prior written permission.  For
22      written permission, please contact <request_AT_jdom_DOT_org>.
23  
24   4. Products derived from this software may not be called "JDOM", nor
25      may "JDOM" appear in their name, without prior written permission
26      from the JDOM Project Management <request_AT_jdom_DOT_org>.
27  
28   In addition, we request (but do not require) that you include in the
29   end-user documentation provided with the redistribution and/or in the
30   software itself an acknowledgement equivalent to the following:
31       "This product includes software developed by the
32        JDOM Project (http://www.jdom.org/)."
33   Alternatively, the acknowledgment may be graphical using the logos
34   available at http://www.jdom.org/images/logos.
35  
36   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39   DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47   SUCH DAMAGE.
48  
49   This software consists of voluntary contributions made by many
50   individuals on behalf of the JDOM Project and was originally
51   created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52   Brett McLaughlin <brett_AT_jdom_DOT_org>.  For more information
53   on the JDOM Project, please see <http://www.jdom.org/>.
54  
55   */
56  
57  package org.jdom.input;
58  
59  import java.util.*;
60  
61  import org.jdom.*;
62  import org.xml.sax.*;
63  import org.xml.sax.ext.*;
64  import org.xml.sax.helpers.*;
65  
66  /**
67   * A support class for {@link SAXBuilder}.
68   *
69   * @version $Revision: 1.68 $, $Date: 2004/08/31 06:14:05 $
70   * @author  Brett McLaughlin
71   * @author  Jason Hunter
72   * @author  Philip Nelson
73   * @author  Bradley S. Huffman
74   * @author  phil@triloggroup.com
75   */
76  public class SAXHandler extends DefaultHandler implements LexicalHandler,
77                                                            DeclHandler,
78                                                            DTDHandler {
79  
80      private static final String CVS_ID =
81        "@(#) $RCSfile: SAXHandler.java,v $ $Revision: 1.68 $ $Date: 2004/08/31 06:14:05 $ $Name: jdom_1_0 $";
82  
83      /** Hash table to map SAX attribute type names to JDOM attribute types. */
84      private static final Map attrNameToTypeMap = new HashMap(13);
85  
86      /** <code>Document</code> object being built */
87      private Document document;
88  
89      /** <code>Element</code> object being built */
90      private Element currentElement;
91  
92      /** Indicator of where in the document we are */
93      private boolean atRoot;
94  
95      /** Indicator of whether we are in the DocType. Note that the DTD consists
96       * of both the internal subset (inside the <!DOCTYPE> tag) and the
97        * external subset (in a separate .dtd file). */
98      private boolean inDTD = false;
99  
100     /** Indicator of whether we are in the internal subset */
101     private boolean inInternalSubset = false;
102 
103     /** Indicator of whether we previously were in a CDATA */
104     private boolean previousCDATA = false;
105 
106     /** Indicator of whether we are in a CDATA */
107     private boolean inCDATA = false;
108 
109     /** Indicator of whether we should expand entities */
110     private boolean expand = true;
111 
112     /** Indicator of whether we are actively suppressing (non-expanding) a
113         current entity */
114     private boolean suppress = false;
115 
116     /** How many nested entities we're currently within */
117     private int entityDepth = 0;  // XXX may not be necessary anymore?
118 
119     /** Temporary holder for namespaces that have been declared with
120       * startPrefixMapping, but are not yet available on the element */
121     private List declaredNamespaces;
122 
123     /** Temporary holder for the internal subset */
124     private StringBuffer internalSubset = new StringBuffer();
125 
126     /** Temporary holder for Text and CDATA */
127     private TextBuffer textBuffer = new TextBuffer();
128 
129     /** The external entities defined in this document */
130     private Map externalEntities;
131 
132     /** The JDOMFactory used for JDOM object creation */
133     private JDOMFactory factory;
134 
135     /** Whether to ignore ignorable whitespace */
136     private boolean ignoringWhite = false;
137 
138     /** The SAX Locator object provided by the parser */
139     private Locator locator;
140 
141     /**
142      * Class initializer: Populate a table to translate SAX attribute
143      * type names into JDOM attribute type value (integer).
144      * <p>
145      * <b>Note that all the mappings defined below are compliant with
146      * the SAX 2.0 specification exception for "ENUMERATION" with is
147      * specific to Crimson 1.1.X and Xerces 2.0.0-betaX which report
148      * attributes of enumerated types with a type "ENUMERATION"
149      * instead of the expected "NMTOKEN".
150      * </p>
151      * <p>
152      * Note also that Xerces 1.4.X is not SAX 2.0 compliant either
153      * but handling its case requires
154      * {@link #getAttributeType specific code}.
155      * </p>
156      */
157     static {
158         attrNameToTypeMap.put("CDATA",
159                               new Integer(Attribute.CDATA_TYPE));
160         attrNameToTypeMap.put("ID",
161                               new Integer(Attribute.ID_TYPE));
162         attrNameToTypeMap.put("IDREF",
163                               new Integer(Attribute.IDREF_TYPE));
164         attrNameToTypeMap.put("IDREFS",
165                               new Integer(Attribute.IDREFS_TYPE));
166         attrNameToTypeMap.put("ENTITY",
167                               new Integer(Attribute.ENTITY_TYPE));
168         attrNameToTypeMap.put("ENTITIES",
169                               new Integer(Attribute.ENTITIES_TYPE));
170         attrNameToTypeMap.put("NMTOKEN",
171                               new Integer(Attribute.NMTOKEN_TYPE));
172         attrNameToTypeMap.put("NMTOKENS",
173                               new Integer(Attribute.NMTOKENS_TYPE));
174         attrNameToTypeMap.put("NOTATION",
175                               new Integer(Attribute.NOTATION_TYPE));
176         attrNameToTypeMap.put("ENUMERATION",
177                               new Integer(Attribute.ENUMERATED_TYPE));
178     }
179 
180     /**
181      * This will create a new <code>SAXHandler</code> that listens to SAX
182      * events and creates a JDOM Document.  The objects will be constructed
183      * using the default factory.
184      */
185     public SAXHandler() {
186         this(null);
187     }
188 
189     /**
190      * This will create a new <code>SAXHandler</code> that listens to SAX
191      * events and creates a JDOM Document.  The objects will be constructed
192      * using the provided factory.
193      *
194      * @param factory <code>JDOMFactory</code> to be used for constructing
195      * objects
196      */
197     public SAXHandler(JDOMFactory factory) {
198         if (factory != null) {
199             this.factory = factory;
200         } else {
201             this.factory = new DefaultJDOMFactory();
202         }
203 
204         atRoot = true;
205         declaredNamespaces = new ArrayList();
206         externalEntities = new HashMap();
207 
208         document = this.factory.document(null);
209     }
210 
211     /**
212      * Pushes an element onto the tree under construction.  Allows subclasses
213      * to put content under a dummy root element which is useful for building
214      * content that would otherwise be a non-well formed document.
215      *
216      * @param element root element under which content will be built
217      */
218     protected void pushElement(Element element) {
219         if (atRoot) {
220             document.setRootElement(element);  // XXX should we use a factory call?
221             atRoot = false;
222         }
223         else {
224             factory.addContent(currentElement, element);
225         }
226         currentElement = element;
227     }
228 
229     /**
230      * Returns the document.  Should be called after parsing is complete.
231      *
232      * @return <code>Document</code> - Document that was built
233      */
234     public Document getDocument() {
235         return document;
236     }
237 
238     /**
239      * Returns the factory used for constructing objects.
240      *
241      * @return <code>JDOMFactory</code> - the factory used for
242      * constructing objects.
243      *
244      * @see #SAXHandler(org.jdom.JDOMFactory)
245      */
246     public JDOMFactory getFactory() {
247         return factory;
248     }
249 
250     /**
251      * This sets whether or not to expand entities during the build.
252      * A true means to expand entities as normal content.  A false means to
253      * leave entities unexpanded as <code>EntityRef</code> objects.  The
254      * default is true.
255      *
256      * @param expand <code>boolean</code> indicating whether entity expansion
257      * should occur.
258      */
259     public void setExpandEntities(boolean expand) {
260         this.expand = expand;
261     }
262 
263     /**
264      * Returns whether or not entities will be expanded during the
265      * build.
266      *
267      * @return <code>boolean</code> - whether entity expansion
268      * will occur during build.
269      *
270      * @see #setExpandEntities
271      */
272     public boolean getExpandEntities() {
273         return expand;
274     }
275 
276     /**
277      * Specifies whether or not the parser should elminate whitespace in
278      * element content (sometimes known as "ignorable whitespace") when
279      * building the document.  Only whitespace which is contained within
280      * element content that has an element only content model will be
281      * eliminated (see XML Rec 3.2.1).  For this setting to take effect
282      * requires that validation be turned on.  The default value of this
283      * setting is <code>false</code>.
284      *
285      * @param ignoringWhite Whether to ignore ignorable whitespace
286      */
287     public void setIgnoringElementContentWhitespace(boolean ignoringWhite) {
288         this.ignoringWhite = ignoringWhite;
289     }
290 
291     /**
292      * Returns whether or not the parser will elminate whitespace in
293      * element content (sometimes known as "ignorable whitespace") when
294      * building the document.
295      *
296      * @return <code>boolean</code> - whether ignorable whitespace will
297      * be ignored during build.
298      *
299      * @see #setIgnoringElementContentWhitespace
300      */
301     public boolean getIgnoringElementContentWhitespace() {
302         return ignoringWhite;
303     }
304 
305     public void startDocument() {
306         if (locator != null) {
307             document.setBaseURI(locator.getSystemId());
308         }
309     }
310 
311     /**
312      * This is called when the parser encounters an external entity
313      * declaration.
314      *
315      * @param name entity name
316      * @param publicID public id
317      * @param systemID system id
318      * @throws SAXException when things go wrong
319      */
320     public void externalEntityDecl(String name,
321                                    String publicID, String systemID)
322                                    throws SAXException {
323         // Store the public and system ids for the name
324         externalEntities.put(name, new String[]{publicID, systemID});
325 
326         if (!inInternalSubset) return;
327 
328         internalSubset.append("  <!ENTITY ")
329               .append(name);
330         appendExternalId(publicID, systemID);
331         internalSubset.append(">\n");
332     }
333 
334     /**
335      * This handles an attribute declaration in the internal subset.
336      *
337      * @param eName <code>String</code> element name of attribute
338      * @param aName <code>String</code> attribute name
339      * @param type <code>String</code> attribute type
340      * @param valueDefault <code>String</code> default value of attribute
341      * @param value <code>String</code> value of attribute
342      * @throws SAXException
343      */
344     public void attributeDecl(String eName, String aName, String type,
345                               String valueDefault, String value)
346         throws SAXException {
347 
348         if (!inInternalSubset) return;
349 
350         internalSubset.append("  <!ATTLIST ")
351               .append(eName)
352               .append(' ')
353               .append(aName)
354               .append(' ')
355               .append(type)
356               .append(' ');
357         if (valueDefault != null) {
358               internalSubset.append(valueDefault);
359         } else {
360             internalSubset.append('\"')
361                   .append(value)
362                   .append('\"');
363         }
364         if ((valueDefault != null) && (valueDefault.equals("#FIXED"))) {
365             internalSubset.append(" \"")
366                   .append(value)
367                   .append('\"');
368         }
369         internalSubset.append(">\n");
370     }
371 
372     /**
373      * Handle an element declaration in a DTD.
374      *
375      * @param name <code>String</code> name of element
376      * @param model <code>String</code> model of the element in DTD syntax
377      * @throws SAXException
378      */
379     public void elementDecl(String name, String model) throws SAXException {
380         // Skip elements that come from the external subset
381         if (!inInternalSubset) return;
382 
383         internalSubset.append("  <!ELEMENT ")
384               .append(name)
385               .append(' ')
386               .append(model)
387               .append(">\n");
388     }
389 
390     /**
391      * Handle an internal entity declaration in a DTD.
392      *
393      * @param name <code>String</code> name of entity
394      * @param value <code>String</code> value of the entity
395      * @throws SAXException
396      */
397     public void internalEntityDecl(String name, String value)
398         throws SAXException {
399 
400         // Skip entities that come from the external subset
401         if (!inInternalSubset) return;
402 
403         internalSubset.append("  <!ENTITY ");
404         if (name.startsWith("%")) {
405            internalSubset.append("% ").append(name.substring(1));
406         } else {
407            internalSubset.append(name);
408         }
409         internalSubset.append(" \"")
410               .append(value)
411               .append("\">\n");
412     }
413 
414     /**
415      * This will indicate that a processing instruction has been encountered.
416      * (The XML declaration is not a processing instruction and will not
417      * be reported.)
418      *
419      * @param target <code>String</code> target of PI
420      * @param data <code>String</code> containing all data sent to the PI.
421      *             This typically looks like one or more attribute value
422      *             pairs.
423      * @throws SAXException when things go wrong
424      */
425     public void processingInstruction(String target, String data)
426         throws SAXException {
427 
428         if (suppress) return;
429 
430         flushCharacters();
431 
432         if (atRoot) {
433             factory.addContent(document, factory.processingInstruction(target, data));
434         } else {
435             factory.addContent(getCurrentElement(),
436                 factory.processingInstruction(target, data));
437         }
438     }
439 
440     /**
441      * This indicates that an unresolvable entity reference has been
442      * encountered, normally because the external DTD subset has not been
443      * read.
444      *
445      * @param name <code>String</code> name of entity
446      * @throws SAXException when things go wrong
447      */
448     public void skippedEntity(String name)
449         throws SAXException {
450 
451         // We don't handle parameter entity references.
452         if (name.startsWith("%")) return;
453 
454         flushCharacters();
455 
456         factory.addContent(getCurrentElement(), factory.entityRef(name));
457     }
458 
459     /**
460      * This will add the prefix mapping to the JDOM
461      * <code>Document</code> object.
462      *
463      * @param prefix <code>String</code> namespace prefix.
464      * @param uri <code>String</code> namespace URI.
465      */
466     public void startPrefixMapping(String prefix, String uri)
467         throws SAXException {
468 
469         if (suppress) return;
470 
471         Namespace ns = Namespace.getNamespace(prefix, uri);
472         declaredNamespaces.add(ns);
473     }
474 
475     /**
476      * This reports the occurrence of an actual element.  It will include
477      * the element's attributes, with the exception of XML vocabulary
478      * specific attributes, such as
479      * <code>xmlns:[namespace prefix]</code> and
480      * <code>xsi:schemaLocation</code>.
481      *
482      * @param namespaceURI <code>String</code> namespace URI this element
483      *                     is associated with, or an empty
484      *                     <code>String</code>
485      * @param localName <code>String</code> name of element (with no
486      *                  namespace prefix, if one is present)
487      * @param qName <code>String</code> XML 1.0 version of element name:
488      *                [namespace prefix]:[localName]
489      * @param atts <code>Attributes</code> list for this element
490      * @throws SAXException when things go wrong
491      */
492     public void startElement(String namespaceURI, String localName,
493                              String qName, Attributes atts)
494                              throws SAXException {
495         if (suppress) return;
496 
497         Element element = null;
498 
499         if ((namespaceURI != null) && (!namespaceURI.equals(""))) {
500             String prefix = "";
501 
502             // Determine any prefix on the Element
503             if (!qName.equals(localName)) {
504                 int split = qName.indexOf(":");
505                 prefix = qName.substring(0, split);
506             }
507             Namespace elementNamespace =
508                 Namespace.getNamespace(prefix, namespaceURI);
509             element = factory.element(localName, elementNamespace);
510         } else {
511             element = factory.element(localName);
512         }
513 
514         // Take leftover declared namespaces and add them to this element's
515         // map of namespaces
516         if (declaredNamespaces.size() > 0) {
517             transferNamespaces(element);
518         }
519 
520         // Handle attributes
521         for (int i=0, len=atts.getLength(); i<len; i++) {
522             Attribute attribute = null;
523 
524             String attLocalName = atts.getLocalName(i);
525             String attQName = atts.getQName(i);
526             int attType = getAttributeType(atts.getType(i));
527 
528             // Bypass any xmlns attributes which might appear, as we got
529             // them already in startPrefixMapping().
530             // This is sometimes necessary when SAXHandler is used with
531             // another source than SAXBuilder, as with JDOMResult.
532             if (attQName.startsWith("xmlns:") || attQName.equals("xmlns")) {
533                 continue;
534             }
535 
536             if (!attQName.equals(attLocalName)) {
537                 String attPrefix = attQName.substring(0, attQName.indexOf(":"));
538                 Namespace attNs = Namespace.getNamespace(attPrefix,
539                                                          atts.getURI(i));
540 
541                 attribute = factory.attribute(attLocalName, atts.getValue(i),
542                                               attType, attNs);
543             } else {
544                 attribute = factory.attribute(attLocalName, atts.getValue(i),
545                                               attType);
546             }
547             factory.setAttribute(element, attribute);
548         }
549 
550         flushCharacters();
551 
552         if (atRoot) {
553             document.setRootElement(element);  // XXX should we use a factory call?
554             atRoot = false;
555         } else {
556             factory.addContent(getCurrentElement(), element);
557         }
558         currentElement = element;
559     }
560 
561     /**
562      * This will take the supplied <code>{@link Element}</code> and
563      * transfer its namespaces to the global namespace storage.
564      *
565      * @param element <code>Element</code> to read namespaces from.
566      */
567     private void transferNamespaces(Element element) {
568         Iterator i = declaredNamespaces.iterator();
569         while (i.hasNext()) {
570             Namespace ns = (Namespace)i.next();
571             if (ns != element.getNamespace()) {
572                 element.addNamespaceDeclaration(ns);
573             }
574         }
575         declaredNamespaces.clear();
576     }
577 
578     /**
579      * This will report character data (within an element).
580      *
581      * @param ch <code>char[]</code> character array with character data
582      * @param start <code>int</code> index in array where data starts.
583      * @param length <code>int</code> length of data.
584      * @throws SAXException
585      */
586     public void characters(char[] ch, int start, int length)
587                     throws SAXException {
588 
589         if (suppress || (length == 0))
590             return;
591 
592         if (previousCDATA != inCDATA) {
593             flushCharacters();
594         }
595 
596         textBuffer.append(ch, start, length);
597     }
598 
599     /**
600      * Capture ignorable whitespace as text.  If
601      * setIgnoringElementContentWhitespace(true) has been called then this
602      * method does nothing.
603      *
604      * @param ch <code>[]</code> - char array of ignorable whitespace
605      * @param start <code>int</code> - starting position within array
606      * @param length <code>int</code> - length of whitespace after start
607      * @throws SAXException when things go wrong
608      */
609     public void ignorableWhitespace(char[] ch, int start, int length)
610                                                      throws SAXException {
611         if (!ignoringWhite) {
612             characters(ch, start, length);
613         }
614     }
615 
616     /**
617      * This will flush any characters from SAX character calls we've
618      * been buffering.
619      *
620      * @throws SAXException when things go wrong
621      */
622     protected void flushCharacters() throws SAXException {
623         flushCharacters(textBuffer.toString());
624         textBuffer.clear();
625     }
626 
627     /**
628      * Flush the given string into the document.  This is a protected method
629      * so subclassers can control text handling without knowledge of the
630      * internals of this class.
631      *
632      * @param data string to flush
633      */
634     protected void flushCharacters(String data) throws SAXException {
635         if (data.length() == 0) {
636             previousCDATA = inCDATA;
637             return;
638         }
639 
640 /**
641  * This is commented out because of some problems with
642  * the inline DTDs that Xerces seems to have.
643 if (!inDTD) {
644   if (inEntity) {
645     getCurrentElement().setContent(factory.text(data));
646   } else {
647     getCurrentElement().addContent(factory.text(data));
648 }
649 */
650 
651         if (previousCDATA) {
652             factory.addContent(getCurrentElement(), factory.cdata(data));
653         }
654         else {
655             factory.addContent(getCurrentElement(), factory.text(data));
656         }
657 
658         previousCDATA = inCDATA;
659     }
660 
661     /**
662      * Indicates the end of an element
663      * (<code>&lt;/[element name]&gt;</code>) is reached.  Note that
664      * the parser does not distinguish between empty
665      * elements and non-empty elements, so this will occur uniformly.
666      *
667      * @param namespaceURI <code>String</code> URI of namespace this
668      *                     element is associated with
669      * @param localName <code>String</code> name of element without prefix
670      * @param qName <code>String</code> name of element in XML 1.0 form
671      * @throws SAXException when things go wrong
672      */
673     public void endElement(String namespaceURI, String localName,
674                            String qName) throws SAXException {
675 
676         if (suppress) return;
677 
678         flushCharacters();
679 
680         if (!atRoot) {
681             Parent p = currentElement.getParent();
682             if (p instanceof Document) {
683                atRoot = true;
684             }
685             else {
686                 currentElement = (Element) p;
687             }
688         }
689         else {
690             throw new SAXException(
691                 "Ill-formed XML document (missing opening tag for " +
692                 localName + ")");
693         }
694     }
695 
696     /**
697      * This will signify that a DTD is being parsed, and can be
698      * used to ensure that comments and other lexical structures
699      * in the DTD are not added to the JDOM <code>Document</code>
700      * object.
701      *
702      * @param name <code>String</code> name of element listed in DTD
703      * @param publicID <code>String</code> public ID of DTD
704      * @param systemID <code>String</code> system ID of DTD
705      */
706     public void startDTD(String name, String publicID, String systemID)
707         throws SAXException {
708 
709         flushCharacters(); // Is this needed here?
710 
711         factory.addContent(document, factory.docType(name, publicID, systemID));
712         inDTD = true;
713         inInternalSubset = true;
714     }
715 
716     /**
717      * This signifies that the reading of the DTD is complete.
718      *
719      * @throws SAXException
720      */
721     public void endDTD() throws SAXException {
722 
723         document.getDocType().setInternalSubset(internalSubset.toString());
724         inDTD = false;
725         inInternalSubset = false;
726     }
727 
728     public void startEntity(String name) throws SAXException {
729         entityDepth++;
730 
731         if (expand || entityDepth > 1) {
732             // Short cut out if we're expanding or if we're nested
733             return;
734         }
735 
736         // A "[dtd]" entity indicates the beginning of the external subset
737         if (name.equals("[dtd]")) {
738             inInternalSubset = false;
739             return;
740         }
741 
742         // Ignore DTD references, and translate the standard 5
743         if ((!inDTD) &&
744             (!name.equals("amp")) &&
745             (!name.equals("lt")) &&
746             (!name.equals("gt")) &&
747             (!name.equals("apos")) &&
748             (!name.equals("quot"))) {
749 
750             if (!expand) {
751                 String pub = null;
752                 String sys = null;
753                 String[] ids = (String[]) externalEntities.get(name);
754                 if (ids != null) {
755                   pub = ids[0];  // may be null, that's OK
756                   sys = ids[1];  // may be null, that's OK
757                 }
758                 /**
759                  * if no current element, this entity belongs to an attribute
760                  * in these cases, it is an error on the part of the parser
761                  * to call startEntity but this will help in some cases.
762                  * See org/xml/sax/ext/LexicalHandler.html#startEntity(java.lang.String)
763                  * for more information
764                  */
765                 if (!atRoot) {
766                     flushCharacters();
767                     EntityRef entity = factory.entityRef(name, pub, sys);
768 
769                     // no way to tell if the entity was from an attribute or element so just assume element
770                     factory.addContent(getCurrentElement(), entity);
771                 }
772                 suppress = true;
773             }
774         }
775     }
776 
777     public void endEntity(String name) throws SAXException {
778         entityDepth--;
779         if (entityDepth == 0) {
780             // No way are we suppressing if not in an entity,
781             // regardless of the "expand" value
782             suppress = false;
783         }
784         if (name.equals("[dtd]")) {
785             inInternalSubset = true;
786         }
787     }
788 
789     /**
790      * Report a CDATA section
791      *
792      * @throws SAXException
793      */
794     public void startCDATA() throws SAXException {
795         if (suppress) return;
796 
797         inCDATA = true;
798     }
799 
800     /**
801      * Report a CDATA section
802      */
803     public void endCDATA() throws SAXException {
804         if (suppress) return;
805 
806         previousCDATA = true;
807         inCDATA = false;
808     }
809 
810     /**
811      * This reports that a comments is parsed.  If not in the
812      * DTD, this comment is added to the current JDOM
813      * <code>Element</code>, or the <code>Document</code> itself
814      * if at that level.
815      *
816      * @param ch <code>ch[]</code> array of comment characters.
817      * @param start <code>int</code> index to start reading from.
818      * @param length <code>int</code> length of data.
819      * @throws SAXException
820      */
821     public void comment(char[] ch, int start, int length)
822         throws SAXException {
823 
824         if (suppress) return;
825 
826         flushCharacters();
827 
828         String commentText = new String(ch, start, length);
829         if (inDTD && inInternalSubset && (expand == false)) {
830             internalSubset.append("  <!--")
831                   .append(commentText)
832                   .append("-->\n");
833             return;
834         }
835         if ((!inDTD) && (!commentText.equals(""))) {
836             if (atRoot) {
837                 factory.addContent(document, factory.comment(commentText));
838             } else {
839                 factory.addContent(getCurrentElement(), factory.comment(commentText));
840             }
841         }
842     }
843 
844     /**
845      * Handle the declaration of a Notation in a DTD
846      *
847      * @param name name of the notation
848      * @param publicID the public ID of the notation
849      * @param systemID the system ID of the notation
850      */
851     public void notationDecl(String name, String publicID, String systemID)
852         throws SAXException {
853 
854         if (!inInternalSubset) return;
855 
856         internalSubset.append("  <!NOTATION ")
857               .append(name);
858         appendExternalId(publicID, systemID);
859         internalSubset.append(">\n");
860     }
861 
862     /**
863      * Handler for unparsed entity declarations in the DTD
864      *
865      * @param name <code>String</code> of the unparsed entity decl
866      * @param publicID <code>String</code> of the unparsed entity decl
867      * @param systemID <code>String</code> of the unparsed entity decl
868      * @param notationName <code>String</code> of the unparsed entity decl
869      */
870     public void unparsedEntityDecl(String name, String publicID,
871                                    String systemID, String notationName)
872         throws SAXException {
873 
874         if (!inInternalSubset) return;
875 
876         internalSubset.append("  <!ENTITY ")
877               .append(name);
878         appendExternalId(publicID, systemID);
879         internalSubset.append(" NDATA ")
880               .append(notationName);
881         internalSubset.append(">\n");
882     }
883 
884     /**
885      * Appends an external ID to the internal subset buffer. Either publicID
886      * or systemID may be null, but not both.
887      *
888      * @param publicID the public ID
889      * @param systemID the system ID
890      */
891     private void appendExternalId(String publicID, String systemID) {
892         if (publicID != null) {
893             internalSubset.append(" PUBLIC \"")
894                   .append(publicID)
895                   .append('\"');
896         }
897         if (systemID != null) {
898             if (publicID == null) {
899                 internalSubset.append(" SYSTEM ");
900             }
901             else {
902                 internalSubset.append(' ');
903             }
904             internalSubset.append('\"')
905                   .append(systemID)
906                   .append('\"');
907         }
908     }
909 
910     /**
911      * Returns the being-parsed element.
912      *
913      * @return <code>Element</code> - element being built.
914      * @throws SAXException
915      */
916     public Element getCurrentElement() throws SAXException {
917         if (currentElement == null) {
918             throw new SAXException(
919                 "Ill-formed XML document (multiple root elements detected)");
920         }
921         return currentElement;
922     }
923 
924     /**
925      * Returns the the JDOM Attribute type value from the SAX 2.0
926      * attribute type string provided by the parser.
927      *
928      * @param typeName <code>String</code> the SAX 2.0 attribute
929      * type string.
930      *
931      * @return <code>int</code> the JDOM attribute type.
932      *
933      * @see Attribute#setAttributeType
934      * @see Attributes#getType
935      */
936     private static int getAttributeType(String typeName) {
937         Integer type = (Integer)(attrNameToTypeMap.get(typeName));
938         if (type == null) {
939             if (typeName != null && typeName.length() > 0 &&
940                 typeName.charAt(0) == '(') {
941                 // Xerces 1.4.X reports attributes of enumerated type with
942                 // a type string equals to the enumeration definition, i.e.
943                 // starting with a parenthesis.
944                 return Attribute.ENUMERATED_TYPE;
945             }
946             else {
947                 return Attribute.UNDECLARED_TYPE;
948             }
949         } else {
950             return type.intValue();
951         }
952     }
953 
954     /**
955      * Receives an object for locating the origin of SAX document
956      * events.  This method is invoked by the SAX parser.
957      * <p>
958      * {@link org.jdom.JDOMFactory} implementations can use the
959      * {@link #getDocumentLocator} method to get access to the
960      * {@link Locator} during parse.
961      * </p>
962      *
963      * @param locator <code>Locator</code> an object that can return
964      * the location of any SAX document event.
965      */
966     public void setDocumentLocator(Locator locator) {
967         this.locator = locator;
968     }
969 
970     /**
971      * Provides access to the {@link Locator} object provided by the
972      * SAX parser.
973      *
974      * @return <code>Locator</code> an object that can return
975      * the location of any SAX document event.
976      */
977     public Locator getDocumentLocator() {
978         return locator;
979     }
980 }