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