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