001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.xerces.impl;
019:
020: import java.io.IOException;
021:
022: import org.apache.xerces.impl.dtd.XMLDTDValidatorFilter;
023: import org.apache.xerces.impl.msg.XMLMessageFormatter;
024: import org.apache.xerces.util.XMLAttributesImpl;
025: import org.apache.xerces.util.XMLSymbols;
026: import org.apache.xerces.xni.NamespaceContext;
027: import org.apache.xerces.xni.QName;
028: import org.apache.xerces.xni.XMLDocumentHandler;
029: import org.apache.xerces.xni.XNIException;
030: import org.apache.xerces.xni.parser.XMLComponentManager;
031: import org.apache.xerces.xni.parser.XMLConfigurationException;
032: import org.apache.xerces.xni.parser.XMLDocumentSource;
033:
034: /**
035: * The scanner acts as the source for the document
036: * information which is communicated to the document handler.
037: *
038: * This class scans an XML document, checks if document has a DTD, and if
039: * DTD is not found the scanner will remove the DTD Validator from the pipeline and perform
040: * namespace binding.
041: *
042: * Note: This scanner should only be used when the namespace processing is on!
043: *
044: * <p>
045: * This component requires the following features and properties from the
046: * component manager that uses it:
047: * <ul>
048: * <li>http://xml.org/sax/features/namespaces {true} -- if the value of this
049: * feature is set to false this scanner must not be used.</li>
050: * <li>http://xml.org/sax/features/validation</li>
051: * <li>http://apache.org/xml/features/nonvalidating/load-external-dtd</li>
052: * <li>http://apache.org/xml/features/scanner/notify-char-refs</li>
053: * <li>http://apache.org/xml/features/scanner/notify-builtin-refs</li>
054: * <li>http://apache.org/xml/properties/internal/symbol-table</li>
055: * <li>http://apache.org/xml/properties/internal/error-reporter</li>
056: * <li>http://apache.org/xml/properties/internal/entity-manager</li>
057: * <li>http://apache.org/xml/properties/internal/dtd-scanner</li>
058: * </ul>
059: *
060: * @xerces.internal
061: *
062: * @author Elena Litani, IBM
063: *
064: * @version $Id: XMLNSDocumentScannerImpl.java 495747 2007-01-12 21:48:00Z mrglavas $
065: */
066: public class XMLNSDocumentScannerImpl extends XMLDocumentScannerImpl {
067:
068: /** If is true, the dtd validator is no longer in the pipeline
069: * and the scanner should bind namespaces */
070: protected boolean fBindNamespaces;
071:
072: /** If validating parser, make sure we report an error in the
073: * scanner if DTD grammar is missing.*/
074: protected boolean fPerformValidation;
075:
076: // private data
077: //
078:
079: /** DTD validator */
080: private XMLDTDValidatorFilter fDTDValidator;
081:
082: /**
083: * Saw spaces after element name or between attributes.
084: *
085: * This is reserved for the case where scanning of a start element spans
086: * several methods, as is the case when scanning the start of a root element
087: * where a DTD external subset may be read after scanning the element name.
088: */
089: private boolean fSawSpace;
090:
091: /**
092: * The scanner is responsible for removing DTD validator
093: * from the pipeline if it is not needed.
094: *
095: * @param dtdValidator The DTDValidator
096: */
097: public void setDTDValidator(XMLDTDValidatorFilter dtdValidator) {
098: fDTDValidator = dtdValidator;
099: }
100:
101: /**
102: * Scans a start element. This method will handle the binding of
103: * namespace information and notifying the handler of the start
104: * of the element.
105: * <p>
106: * <pre>
107: * [44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
108: * [40] STag ::= '<' Name (S Attribute)* S? '>'
109: * </pre>
110: * <p>
111: * <strong>Note:</strong> This method assumes that the leading
112: * '<' character has been consumed.
113: * <p>
114: * <strong>Note:</strong> This method uses the fElementQName and
115: * fAttributes variables. The contents of these variables will be
116: * destroyed. The caller should copy important information out of
117: * these variables before calling this method.
118: *
119: * @return True if element is empty. (i.e. It matches
120: * production [44].
121: */
122: protected boolean scanStartElement() throws IOException,
123: XNIException {
124: if (DEBUG_CONTENT_SCANNING)
125: System.out.println(">>> scanStartElementNS()");
126:
127: // Note: namespace processing is on by default
128: fEntityScanner.scanQName(fElementQName);
129: // REVISIT - [Q] Why do we need this temp variable? -- mrglavas
130: String rawname = fElementQName.rawname;
131: if (fBindNamespaces) {
132: fNamespaceContext.pushContext();
133: if (fScannerState == SCANNER_STATE_ROOT_ELEMENT) {
134: if (fPerformValidation) {
135: fErrorReporter.reportError(
136: XMLMessageFormatter.XML_DOMAIN,
137: "MSG_GRAMMAR_NOT_FOUND",
138: new Object[] { rawname },
139: XMLErrorReporter.SEVERITY_ERROR);
140:
141: if (fDoctypeName == null
142: || !fDoctypeName.equals(rawname)) {
143: fErrorReporter.reportError(
144: XMLMessageFormatter.XML_DOMAIN,
145: "RootElementTypeMustMatchDoctypedecl",
146: new Object[] { fDoctypeName, rawname },
147: XMLErrorReporter.SEVERITY_ERROR);
148: }
149: }
150: }
151: }
152:
153: // push element stack
154: fCurrentElement = fElementStack.pushElement(fElementQName);
155:
156: // attributes
157: boolean empty = false;
158: fAttributes.removeAllAttributes();
159: do {
160: // spaces
161: boolean sawSpace = fEntityScanner.skipSpaces();
162:
163: // end tag?
164: int c = fEntityScanner.peekChar();
165: if (c == '>') {
166: fEntityScanner.scanChar();
167: break;
168: } else if (c == '/') {
169: fEntityScanner.scanChar();
170: if (!fEntityScanner.skipChar('>')) {
171: reportFatalError("ElementUnterminated",
172: new Object[] { rawname });
173: }
174: empty = true;
175: break;
176: } else if (!isValidNameStartChar(c) || !sawSpace) {
177: reportFatalError("ElementUnterminated",
178: new Object[] { rawname });
179: }
180:
181: // attributes
182: scanAttribute(fAttributes);
183:
184: } while (true);
185:
186: if (fBindNamespaces) {
187: // REVISIT: is it required? forbit xmlns prefix for element
188: if (fElementQName.prefix == XMLSymbols.PREFIX_XMLNS) {
189: fErrorReporter.reportError(
190: XMLMessageFormatter.XMLNS_DOMAIN,
191: "ElementXMLNSPrefix",
192: new Object[] { fElementQName.rawname },
193: XMLErrorReporter.SEVERITY_FATAL_ERROR);
194: }
195:
196: // bind the element
197: String prefix = fElementQName.prefix != null ? fElementQName.prefix
198: : XMLSymbols.EMPTY_STRING;
199: // assign uri to the element
200: fElementQName.uri = fNamespaceContext.getURI(prefix);
201: // make sure that object in the element stack is updated as well
202: fCurrentElement.uri = fElementQName.uri;
203:
204: if (fElementQName.prefix == null
205: && fElementQName.uri != null) {
206: fElementQName.prefix = XMLSymbols.EMPTY_STRING;
207: // making sure that the object in the element stack is updated too.
208: fCurrentElement.prefix = XMLSymbols.EMPTY_STRING;
209: }
210: if (fElementQName.prefix != null
211: && fElementQName.uri == null) {
212: fErrorReporter.reportError(
213: XMLMessageFormatter.XMLNS_DOMAIN,
214: "ElementPrefixUnbound", new Object[] {
215: fElementQName.prefix,
216: fElementQName.rawname },
217: XMLErrorReporter.SEVERITY_FATAL_ERROR);
218: }
219:
220: // bind attributes (xmlns are already bound bellow)
221: int length = fAttributes.getLength();
222: // fLength = 0; //initialize structure
223: for (int i = 0; i < length; i++) {
224: fAttributes.getName(i, fAttributeQName);
225:
226: String aprefix = fAttributeQName.prefix != null ? fAttributeQName.prefix
227: : XMLSymbols.EMPTY_STRING;
228: String uri = fNamespaceContext.getURI(aprefix);
229: // REVISIT: try removing the first "if" and see if it is faster.
230: //
231: if (fAttributeQName.uri != null
232: && fAttributeQName.uri == uri) {
233: // checkDuplicates(fAttributeQName, fAttributes);
234: continue;
235: }
236: if (aprefix != XMLSymbols.EMPTY_STRING) {
237: fAttributeQName.uri = uri;
238: if (uri == null) {
239: fErrorReporter.reportError(
240: XMLMessageFormatter.XMLNS_DOMAIN,
241: "AttributePrefixUnbound", new Object[] {
242: fElementQName.rawname,
243: fAttributeQName.rawname,
244: aprefix },
245: XMLErrorReporter.SEVERITY_FATAL_ERROR);
246: }
247: fAttributes.setURI(i, uri);
248: // checkDuplicates(fAttributeQName, fAttributes);
249: }
250: }
251:
252: if (length > 1) {
253: QName name = fAttributes.checkDuplicatesNS();
254: if (name != null) {
255: if (name.uri != null) {
256: fErrorReporter.reportError(
257: XMLMessageFormatter.XMLNS_DOMAIN,
258: "AttributeNSNotUnique", new Object[] {
259: fElementQName.rawname,
260: name.localpart, name.uri },
261: XMLErrorReporter.SEVERITY_FATAL_ERROR);
262: } else {
263: fErrorReporter.reportError(
264: XMLMessageFormatter.XMLNS_DOMAIN,
265: "AttributeNotUnique", new Object[] {
266: fElementQName.rawname,
267: name.rawname },
268: XMLErrorReporter.SEVERITY_FATAL_ERROR);
269: }
270: }
271: }
272: }
273:
274: // call handler
275: if (fDocumentHandler != null) {
276: if (empty) {
277:
278: //decrease the markup depth..
279: fMarkupDepth--;
280:
281: // check that this element was opened in the same entity
282: if (fMarkupDepth < fEntityStack[fEntityDepth - 1]) {
283: reportFatalError("ElementEntityMismatch",
284: new Object[] { fCurrentElement.rawname });
285: }
286:
287: fDocumentHandler.emptyElement(fElementQName,
288: fAttributes, null);
289:
290: if (fBindNamespaces) {
291: fNamespaceContext.popContext();
292: }
293: //pop the element off the stack..
294: fElementStack.popElement(fElementQName);
295: } else {
296: fDocumentHandler.startElement(fElementQName,
297: fAttributes, null);
298: }
299: }
300:
301: if (DEBUG_CONTENT_SCANNING)
302: System.out.println("<<< scanStartElement(): " + empty);
303: return empty;
304:
305: } // scanStartElement():boolean
306:
307: /**
308: * Scans the name of an element in a start or empty tag.
309: *
310: * @see #scanStartElement()
311: */
312: protected void scanStartElementName() throws IOException,
313: XNIException {
314: // Note: namespace processing is on by default
315: fEntityScanner.scanQName(fElementQName);
316: // Must skip spaces here because the DTD scanner
317: // would consume them at the end of the external subset.
318: fSawSpace = fEntityScanner.skipSpaces();
319: } // scanStartElementName()
320:
321: /**
322: * Scans the remainder of a start or empty tag after the element name.
323: *
324: * @see #scanStartElement
325: * @return True if element is empty.
326: */
327: protected boolean scanStartElementAfterName() throws IOException,
328: XNIException {
329:
330: // REVISIT - [Q] Why do we need this temp variable? -- mrglavas
331: String rawname = fElementQName.rawname;
332: if (fBindNamespaces) {
333: fNamespaceContext.pushContext();
334: if (fScannerState == SCANNER_STATE_ROOT_ELEMENT) {
335: if (fPerformValidation) {
336: fErrorReporter.reportError(
337: XMLMessageFormatter.XML_DOMAIN,
338: "MSG_GRAMMAR_NOT_FOUND",
339: new Object[] { rawname },
340: XMLErrorReporter.SEVERITY_ERROR);
341:
342: if (fDoctypeName == null
343: || !fDoctypeName.equals(rawname)) {
344: fErrorReporter.reportError(
345: XMLMessageFormatter.XML_DOMAIN,
346: "RootElementTypeMustMatchDoctypedecl",
347: new Object[] { fDoctypeName, rawname },
348: XMLErrorReporter.SEVERITY_ERROR);
349: }
350: }
351: }
352: }
353:
354: // push element stack
355: fCurrentElement = fElementStack.pushElement(fElementQName);
356:
357: // attributes
358: boolean empty = false;
359: fAttributes.removeAllAttributes();
360: do {
361:
362: // end tag?
363: int c = fEntityScanner.peekChar();
364: if (c == '>') {
365: fEntityScanner.scanChar();
366: break;
367: } else if (c == '/') {
368: fEntityScanner.scanChar();
369: if (!fEntityScanner.skipChar('>')) {
370: reportFatalError("ElementUnterminated",
371: new Object[] { rawname });
372: }
373: empty = true;
374: break;
375: } else if (!isValidNameStartChar(c) || !fSawSpace) {
376: reportFatalError("ElementUnterminated",
377: new Object[] { rawname });
378: }
379:
380: // attributes
381: scanAttribute(fAttributes);
382:
383: // spaces
384: fSawSpace = fEntityScanner.skipSpaces();
385:
386: } while (true);
387:
388: if (fBindNamespaces) {
389: // REVISIT: is it required? forbit xmlns prefix for element
390: if (fElementQName.prefix == XMLSymbols.PREFIX_XMLNS) {
391: fErrorReporter.reportError(
392: XMLMessageFormatter.XMLNS_DOMAIN,
393: "ElementXMLNSPrefix",
394: new Object[] { fElementQName.rawname },
395: XMLErrorReporter.SEVERITY_FATAL_ERROR);
396: }
397:
398: // bind the element
399: String prefix = fElementQName.prefix != null ? fElementQName.prefix
400: : XMLSymbols.EMPTY_STRING;
401: // assign uri to the element
402: fElementQName.uri = fNamespaceContext.getURI(prefix);
403: // make sure that object in the element stack is updated as well
404: fCurrentElement.uri = fElementQName.uri;
405:
406: if (fElementQName.prefix == null
407: && fElementQName.uri != null) {
408: fElementQName.prefix = XMLSymbols.EMPTY_STRING;
409: // making sure that the object in the element stack is updated too.
410: fCurrentElement.prefix = XMLSymbols.EMPTY_STRING;
411: }
412: if (fElementQName.prefix != null
413: && fElementQName.uri == null) {
414: fErrorReporter.reportError(
415: XMLMessageFormatter.XMLNS_DOMAIN,
416: "ElementPrefixUnbound", new Object[] {
417: fElementQName.prefix,
418: fElementQName.rawname },
419: XMLErrorReporter.SEVERITY_FATAL_ERROR);
420: }
421:
422: // bind attributes (xmlns are already bound bellow)
423: int length = fAttributes.getLength();
424: // fLength = 0; //initialize structure
425: for (int i = 0; i < length; i++) {
426: fAttributes.getName(i, fAttributeQName);
427:
428: String aprefix = fAttributeQName.prefix != null ? fAttributeQName.prefix
429: : XMLSymbols.EMPTY_STRING;
430: String uri = fNamespaceContext.getURI(aprefix);
431: // REVISIT: try removing the first "if" and see if it is faster.
432: //
433: if (fAttributeQName.uri != null
434: && fAttributeQName.uri == uri) {
435: // checkDuplicates(fAttributeQName, fAttributes);
436: continue;
437: }
438: if (aprefix != XMLSymbols.EMPTY_STRING) {
439: fAttributeQName.uri = uri;
440: if (uri == null) {
441: fErrorReporter.reportError(
442: XMLMessageFormatter.XMLNS_DOMAIN,
443: "AttributePrefixUnbound", new Object[] {
444: fElementQName.rawname,
445: fAttributeQName.rawname,
446: aprefix },
447: XMLErrorReporter.SEVERITY_FATAL_ERROR);
448: }
449: fAttributes.setURI(i, uri);
450: // checkDuplicates(fAttributeQName, fAttributes);
451: }
452: }
453:
454: if (length > 1) {
455: QName name = fAttributes.checkDuplicatesNS();
456: if (name != null) {
457: if (name.uri != null) {
458: fErrorReporter.reportError(
459: XMLMessageFormatter.XMLNS_DOMAIN,
460: "AttributeNSNotUnique", new Object[] {
461: fElementQName.rawname,
462: name.localpart, name.uri },
463: XMLErrorReporter.SEVERITY_FATAL_ERROR);
464: } else {
465: fErrorReporter.reportError(
466: XMLMessageFormatter.XMLNS_DOMAIN,
467: "AttributeNotUnique", new Object[] {
468: fElementQName.rawname,
469: name.rawname },
470: XMLErrorReporter.SEVERITY_FATAL_ERROR);
471: }
472: }
473: }
474: }
475:
476: // call handler
477: if (fDocumentHandler != null) {
478: if (empty) {
479:
480: //decrease the markup depth..
481: fMarkupDepth--;
482:
483: // check that this element was opened in the same entity
484: if (fMarkupDepth < fEntityStack[fEntityDepth - 1]) {
485: reportFatalError("ElementEntityMismatch",
486: new Object[] { fCurrentElement.rawname });
487: }
488:
489: fDocumentHandler.emptyElement(fElementQName,
490: fAttributes, null);
491:
492: if (fBindNamespaces) {
493: fNamespaceContext.popContext();
494: }
495: //pop the element off the stack..
496: fElementStack.popElement(fElementQName);
497: } else {
498: fDocumentHandler.startElement(fElementQName,
499: fAttributes, null);
500: }
501: }
502:
503: if (DEBUG_CONTENT_SCANNING)
504: System.out.println("<<< scanStartElementAfterName(): "
505: + empty);
506: return empty;
507: } // scanStartElementAfterName()
508:
509: /**
510: * Scans an attribute.
511: * <p>
512: * <pre>
513: * [41] Attribute ::= Name Eq AttValue
514: * </pre>
515: * <p>
516: * <strong>Note:</strong> This method assumes that the next
517: * character on the stream is the first character of the attribute
518: * name.
519: * <p>
520: * <strong>Note:</strong> This method uses the fAttributeQName and
521: * fQName variables. The contents of these variables will be
522: * destroyed.
523: *
524: * @param attributes The attributes list for the scanned attribute.
525: */
526: protected void scanAttribute(XMLAttributesImpl attributes)
527: throws IOException, XNIException {
528: if (DEBUG_CONTENT_SCANNING)
529: System.out.println(">>> scanAttribute()");
530:
531: // name
532: fEntityScanner.scanQName(fAttributeQName);
533:
534: // equals
535: fEntityScanner.skipSpaces();
536: if (!fEntityScanner.skipChar('=')) {
537: reportFatalError("EqRequiredInAttribute", new Object[] {
538: fCurrentElement.rawname, fAttributeQName.rawname });
539: }
540: fEntityScanner.skipSpaces();
541:
542: // content
543: int attrIndex;
544:
545: if (fBindNamespaces) {
546: attrIndex = attributes.getLength();
547: attributes.addAttributeNS(fAttributeQName,
548: XMLSymbols.fCDATASymbol, null);
549: } else {
550: int oldLen = attributes.getLength();
551: attrIndex = attributes.addAttribute(fAttributeQName,
552: XMLSymbols.fCDATASymbol, null);
553:
554: // WFC: Unique Att Spec
555: if (oldLen == attributes.getLength()) {
556: reportFatalError("AttributeNotUnique", new Object[] {
557: fCurrentElement.rawname,
558: fAttributeQName.rawname });
559: }
560: }
561:
562: // Scan attribute value and return true if the non-normalized and normalized value are the same
563: boolean isSameNormalizedAttr = scanAttributeValue(
564: this .fTempString, fTempString2,
565: fAttributeQName.rawname, fIsEntityDeclaredVC,
566: fCurrentElement.rawname);
567:
568: String value = fTempString.toString();
569: attributes.setValue(attrIndex, value);
570: // If the non-normalized and normalized value are the same, avoid creating a new string.
571: if (!isSameNormalizedAttr) {
572: attributes.setNonNormalizedValue(attrIndex, fTempString2
573: .toString());
574: }
575: attributes.setSpecified(attrIndex, true);
576:
577: // record namespace declarations if any.
578: if (fBindNamespaces) {
579:
580: String localpart = fAttributeQName.localpart;
581: String prefix = fAttributeQName.prefix != null ? fAttributeQName.prefix
582: : XMLSymbols.EMPTY_STRING;
583: // when it's of form xmlns="..." or xmlns:prefix="...",
584: // it's a namespace declaration. but prefix:xmlns="..." isn't.
585: if (prefix == XMLSymbols.PREFIX_XMLNS
586: || prefix == XMLSymbols.EMPTY_STRING
587: && localpart == XMLSymbols.PREFIX_XMLNS) {
588:
589: // get the internalized value of this attribute
590: String uri = fSymbolTable.addSymbol(value);
591:
592: // 1. "xmlns" can't be bound to any namespace
593: if (prefix == XMLSymbols.PREFIX_XMLNS
594: && localpart == XMLSymbols.PREFIX_XMLNS) {
595: fErrorReporter.reportError(
596: XMLMessageFormatter.XMLNS_DOMAIN,
597: "CantBindXMLNS",
598: new Object[] { fAttributeQName },
599: XMLErrorReporter.SEVERITY_FATAL_ERROR);
600: }
601:
602: // 2. the namespace for "xmlns" can't be bound to any prefix
603: if (uri == NamespaceContext.XMLNS_URI) {
604: fErrorReporter.reportError(
605: XMLMessageFormatter.XMLNS_DOMAIN,
606: "CantBindXMLNS",
607: new Object[] { fAttributeQName },
608: XMLErrorReporter.SEVERITY_FATAL_ERROR);
609: }
610:
611: // 3. "xml" can't be bound to any other namespace than it's own
612: if (localpart == XMLSymbols.PREFIX_XML) {
613: if (uri != NamespaceContext.XML_URI) {
614: fErrorReporter.reportError(
615: XMLMessageFormatter.XMLNS_DOMAIN,
616: "CantBindXML",
617: new Object[] { fAttributeQName },
618: XMLErrorReporter.SEVERITY_FATAL_ERROR);
619: }
620: }
621: // 4. the namespace for "xml" can't be bound to any other prefix
622: else {
623: if (uri == NamespaceContext.XML_URI) {
624: fErrorReporter.reportError(
625: XMLMessageFormatter.XMLNS_DOMAIN,
626: "CantBindXML",
627: new Object[] { fAttributeQName },
628: XMLErrorReporter.SEVERITY_FATAL_ERROR);
629: }
630: }
631:
632: prefix = localpart != XMLSymbols.PREFIX_XMLNS ? localpart
633: : XMLSymbols.EMPTY_STRING;
634:
635: // http://www.w3.org/TR/1999/REC-xml-names-19990114/#dt-prefix
636: // We should only report an error if there is a prefix,
637: // that is, the local part is not "xmlns". -SG
638: if (uri == XMLSymbols.EMPTY_STRING
639: && localpart != XMLSymbols.PREFIX_XMLNS) {
640: fErrorReporter.reportError(
641: XMLMessageFormatter.XMLNS_DOMAIN,
642: "EmptyPrefixedAttName",
643: new Object[] { fAttributeQName },
644: XMLErrorReporter.SEVERITY_FATAL_ERROR);
645: }
646:
647: // declare prefix in context
648: fNamespaceContext.declarePrefix(prefix,
649: uri.length() != 0 ? uri : null);
650: // bind namespace attribute to a namespace
651: attributes.setURI(attrIndex, fNamespaceContext
652: .getURI(XMLSymbols.PREFIX_XMLNS));
653:
654: } else {
655: // attempt to bind attribute
656: if (fAttributeQName.prefix != null) {
657: attributes.setURI(attrIndex, fNamespaceContext
658: .getURI(fAttributeQName.prefix));
659: }
660: }
661: }
662:
663: if (DEBUG_CONTENT_SCANNING)
664: System.out.println("<<< scanAttribute()");
665: } // scanAttribute(XMLAttributes)
666:
667: /**
668: * Scans an end element.
669: * <p>
670: * <pre>
671: * [42] ETag ::= '</' Name S? '>'
672: * </pre>
673: * <p>
674: * <strong>Note:</strong> This method uses the fElementQName variable.
675: * The contents of this variable will be destroyed. The caller should
676: * copy the needed information out of this variable before calling
677: * this method.
678: *
679: * @return The element depth.
680: */
681: protected int scanEndElement() throws IOException, XNIException {
682: if (DEBUG_CONTENT_SCANNING)
683: System.out.println(">>> scanEndElement()");
684:
685: // pop context
686: fElementStack.popElement(fElementQName);
687:
688: // Take advantage of the fact that next string _should_ be "fElementQName.rawName",
689: //In scanners most of the time is consumed on checks done for XML characters, we can
690: // optimize on it and avoid the checks done for endElement,
691: //we will also avoid symbol table lookup - neeraj.bajaj@sun.com
692:
693: // this should work both for namespace processing true or false...
694:
695: //REVISIT: if the string is not the same as expected.. we need to do better error handling..
696: //We can skip this for now... In any case if the string doesn't match -- document is not well formed.
697: if (!fEntityScanner.skipString(fElementQName.rawname)) {
698: reportFatalError("ETagRequired",
699: new Object[] { fElementQName.rawname });
700: }
701:
702: // end
703: fEntityScanner.skipSpaces();
704: if (!fEntityScanner.skipChar('>')) {
705: reportFatalError("ETagUnterminated",
706: new Object[] { fElementQName.rawname });
707: }
708: fMarkupDepth--;
709:
710: //we have increased the depth for two markup "<" characters
711: fMarkupDepth--;
712:
713: // check that this element was opened in the same entity
714: if (fMarkupDepth < fEntityStack[fEntityDepth - 1]) {
715: reportFatalError("ElementEntityMismatch",
716: new Object[] { fCurrentElement.rawname });
717: }
718:
719: // call handler
720: if (fDocumentHandler != null) {
721:
722: fDocumentHandler.endElement(fElementQName, null);
723: if (fBindNamespaces) {
724: fNamespaceContext.popContext();
725: }
726:
727: }
728:
729: return fMarkupDepth;
730:
731: } // scanEndElement():int
732:
733: public void reset(XMLComponentManager componentManager)
734: throws XMLConfigurationException {
735:
736: super .reset(componentManager);
737: fPerformValidation = false;
738: fBindNamespaces = false;
739: }
740:
741: /** Creates a content dispatcher. */
742: protected Dispatcher createContentDispatcher() {
743: return new NSContentDispatcher();
744: } // createContentDispatcher():Dispatcher
745:
746: /**
747: * Dispatcher to handle content scanning.
748: */
749: protected final class NSContentDispatcher extends ContentDispatcher {
750:
751: /**
752: * Scan for root element hook. This method is a hook for
753: * subclasses to add code that handles scanning for the root
754: * element. This method will also attempt to remove DTD validator
755: * from the pipeline, if there is no DTD grammar. If DTD validator
756: * is no longer in the pipeline bind namespaces in the scanner.
757: *
758: *
759: * @return True if the caller should stop and return true which
760: * allows the scanner to switch to a new scanning
761: * dispatcher. A return value of false indicates that
762: * the content dispatcher should continue as normal.
763: */
764: protected boolean scanRootElementHook() throws IOException,
765: XNIException {
766:
767: if (fExternalSubsetResolver != null && !fSeenDoctypeDecl
768: && !fDisallowDoctype
769: && (fValidation || fLoadExternalDTD)) {
770: scanStartElementName();
771: resolveExternalSubsetAndRead();
772: reconfigurePipeline();
773: if (scanStartElementAfterName()) {
774: setScannerState(SCANNER_STATE_TRAILING_MISC);
775: setDispatcher(fTrailingMiscDispatcher);
776: return true;
777: }
778: } else {
779: reconfigurePipeline();
780: if (scanStartElement()) {
781: setScannerState(SCANNER_STATE_TRAILING_MISC);
782: setDispatcher(fTrailingMiscDispatcher);
783: return true;
784: }
785: }
786: return false;
787:
788: } // scanRootElementHook():boolean
789:
790: /**
791: * Re-configures pipeline by removing the DTD validator
792: * if no DTD grammar exists. If no validator exists in the
793: * pipeline or there is no DTD grammar, namespace binding
794: * is performed by the scanner in the enclosing class.
795: */
796: private void reconfigurePipeline() {
797: if (fDTDValidator == null) {
798: fBindNamespaces = true;
799: } else if (!fDTDValidator.hasGrammar()) {
800: fBindNamespaces = true;
801: fPerformValidation = fDTDValidator.validate();
802: // re-configure pipeline
803: XMLDocumentSource source = fDTDValidator
804: .getDocumentSource();
805: XMLDocumentHandler handler = fDTDValidator
806: .getDocumentHandler();
807: source.setDocumentHandler(handler);
808: if (handler != null)
809: handler.setDocumentSource(source);
810: fDTDValidator.setDocumentSource(null);
811: fDTDValidator.setDocumentHandler(null);
812: }
813: } // reconfigurePipeline()
814: }
815:
816: } // class XMLNSDocumentScannerImpl
|