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></[element name]></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 }