| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 1999,2000 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Xerces" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * nor may "Apache" appear in their name, without prior written |
| * permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation and was |
| * originally based on software copyright (c) 1999, International |
| * Business Machines, Inc., http://www.apache.org. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| package org.apache.xerces.framework; |
| |
| import org.apache.xerces.readers.XMLEntityHandler; |
| import org.apache.xerces.readers.DefaultEntityHandler; |
| import org.apache.xerces.utils.ChunkyCharArray; |
| import org.apache.xerces.utils.QName; |
| import org.apache.xerces.utils.StringPool; |
| import org.apache.xerces.utils.XMLCharacterProperties; |
| import org.apache.xerces.utils.XMLMessages; |
| import org.apache.xerces.validators.common.GrammarResolver; |
| |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXParseException; |
| |
| /** |
| * This class recognizes most of the grammer for an XML processor. |
| * Additional support is provided by the XMLEntityHandler, via the |
| * XMLEntityReader instances it creates, which are used to process |
| * simple constructs like string literals and character data between |
| * markup. The XMLDTDScanner class contains the remaining support |
| * for the grammer of DTD declarations. When a <!DOCTYPE ...> is |
| * found in the document, the scanDoctypeDecl method will then be |
| * called and the XMLDocumentScanner subclass is responsible for |
| * "connecting" that method to the corresponding method provided |
| * by the XMLDTDScanner class. |
| * |
| * @version $Id$ |
| */ |
| public final class XMLDocumentScanner { |
| // |
| // Constants |
| // |
| |
| // |
| // These character arrays are used as parameters for calls to the |
| // XMLEntityHandler.EntityReader skippedString() method. Some have |
| // package access for use by the inner dispatcher classes. |
| // |
| |
| // |
| // [19] CDStart ::= '<![CDATA[' |
| // |
| static final char[] cdata_string = { '[','C','D','A','T','A','[' }; |
| // |
| // [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' |
| // [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>' |
| // |
| static final char[] xml_string = { 'x','m','l' }; |
| // |
| // [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ") |
| // |
| private static final char[] version_string = { 'v','e','r','s','i','o','n' }; |
| // |
| // [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? |
| // ('[' (markupdecl | PEReference | S)* ']' S?)? '>' |
| // |
| static final char[] doctype_string = { 'D','O','C','T','Y','P','E' }; |
| // |
| // [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") |
| // | ('"' ('yes' | 'no') '"')) |
| // |
| private static final char[] standalone_string = { 's','t','a','n','d','a','l','o','n','e' }; |
| // |
| // [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" ) |
| // |
| private static final char[] encoding_string = { 'e','n','c','o','d','i','n','g' }; |
| |
| /* |
| * Return values for the EventHandler scanAttValue method. |
| */ |
| public static final int |
| RESULT_SUCCESS = 0, |
| RESULT_FAILURE = -1, |
| RESULT_DUPLICATE_ATTR = -2; |
| |
| /** Scanner states */ |
| static final int |
| SCANNER_STATE_XML_DECL = 0, |
| SCANNER_STATE_START_OF_MARKUP = 1, |
| SCANNER_STATE_COMMENT = 2, |
| SCANNER_STATE_PI = 3, |
| SCANNER_STATE_DOCTYPE = 4, |
| SCANNER_STATE_PROLOG = 5, |
| SCANNER_STATE_ROOT_ELEMENT = 6, |
| SCANNER_STATE_CONTENT = 7, |
| SCANNER_STATE_REFERENCE = 8, |
| SCANNER_STATE_ATTRIBUTE_LIST = 9, |
| SCANNER_STATE_ATTRIBUTE_NAME = 10, |
| SCANNER_STATE_ATTRIBUTE_VALUE = 11, |
| SCANNER_STATE_TRAILING_MISC = 12, |
| SCANNER_STATE_END_OF_INPUT = 13, |
| SCANNER_STATE_TERMINATED = 14; |
| |
| // |
| // Instance Variables |
| // |
| /***/ |
| // NOTE: Used by old implementation of scanElementType method. -Ac |
| private StringPool.CharArrayRange fCurrentElementCharArrayRange = null; |
| /***/ |
| int fAttrListHandle = -1; |
| XMLAttrList fAttrList = null; |
| GrammarResolver fGrammarResolver = null; |
| XMLDTDScanner fDTDScanner = null; |
| boolean fNamespacesEnabled = false; |
| boolean fValidationEnabled = false; |
| QName fElementQName = new QName(); |
| QName fAttributeQName = new QName(); |
| QName fCurrentElementQName = new QName(); |
| ScannerDispatcher fDispatcher = null; |
| EventHandler fEventHandler = null; |
| XMLDocumentHandler.DTDHandler fDTDHandler = null; |
| StringPool fStringPool = null; |
| XMLErrorReporter fErrorReporter = null; |
| XMLEntityHandler fEntityHandler = null; |
| XMLEntityHandler.EntityReader fEntityReader = null; |
| XMLEntityHandler.CharBuffer fLiteralData = null; |
| boolean fSeenRootElement = false; |
| boolean fSeenDoctypeDecl = false; |
| boolean fStandalone = false; |
| boolean fParseTextDecl = false; |
| boolean fScanningDTD = false; |
| int fScannerState = SCANNER_STATE_XML_DECL; |
| int fReaderId = -1; |
| int fAttValueReader = -1; |
| int fAttValueElementType = -1; |
| int fAttValueAttrName = -1; |
| int fAttValueOffset = -1; |
| int fAttValueMark = -1; |
| int fScannerMarkupDepth = 0; |
| |
| // |
| // Interfaces |
| // |
| |
| /** |
| * This interface must be implemented by the users of the XMLDocumentScanner class. |
| * These methods form the abstraction between the implementation semantics and the |
| * more generic task of scanning the XML non-DTD grammar. |
| */ |
| public interface EventHandler { |
| /** |
| * Signal standalone = "yes" |
| * |
| * @exception java.lang.Exception |
| */ |
| public void callStandaloneIsYes() throws Exception; |
| |
| /** |
| * Signal the start of a document |
| * |
| * @exception java.lang.Exception |
| */ |
| public void callStartDocument() throws Exception; |
| /** |
| * Signal the end of a document |
| * |
| * @exception java.lang.Exception |
| */ |
| public void callEndDocument() throws Exception; |
| /** |
| * Signal the XML declaration of a document |
| * |
| * @param version the handle in the string pool for the version number |
| * @param encoding the handle in the string pool for the encoding |
| * @param standalong the handle in the string pool for the standalone value |
| * @exception java.lang.Exception |
| */ |
| public void callXMLDecl(int version, int encoding, int standalone) throws Exception; |
| /** |
| * Signal the Text declaration of an external entity. |
| * |
| * @param version the handle in the string pool for the version number |
| * @param encoding the handle in the string pool for the encoding |
| * @exception java.lang.Exception |
| */ |
| public void callTextDecl(int version, int encoding) throws Exception; |
| /** |
| * signal the scanning of a start element tag |
| * |
| * @param element Element name scanned. |
| * @exception java.lang.Exception |
| */ |
| public void callStartElement(QName element) throws Exception; |
| /** |
| * Signal the scanning of an element name in a start element tag. |
| * |
| * @param element Element name scanned. |
| */ |
| public void element(QName element) throws Exception; |
| /** |
| * Signal the scanning of an attribute associated to the previous |
| * start element tag. |
| * |
| * @param element Element name scanned. |
| * @param attrName Attribute name scanned. |
| * @param attrValue The string pool index of the attribute value. |
| */ |
| public boolean attribute(QName element, QName attrName, int attrValue) throws Exception; |
| /** |
| * signal the scanning of an end element tag |
| * |
| * @param readerId the Id of the reader being used to scan the end tag. |
| * @exception java.lang.Exception |
| */ |
| public void callEndElement(int readerId) throws Exception; |
| /** |
| * Signal the start of a CDATA section |
| * @exception java.lang.Exception |
| */ |
| public void callStartCDATA() throws Exception; |
| /** |
| * Signal the end of a CDATA section |
| * @exception java.lang.Exception |
| */ |
| public void callEndCDATA() throws Exception; |
| /** |
| * Report the scanning of character data |
| * |
| * @param ch the handle in the string pool of the character data that was scanned |
| * @exception java.lang.Exception |
| */ |
| public void callCharacters(int ch) throws Exception; |
| /** |
| * Report the scanning of a processing instruction |
| * |
| * @param piTarget the handle in the string pool of the processing instruction targe |
| * @param piData the handle in the string pool of the processing instruction data |
| * @exception java.lang.Exception |
| */ |
| public void callProcessingInstruction(int piTarget, int piData) throws Exception; |
| /** |
| * Report the scanning of a comment |
| * |
| * @param data the handle in the string pool of the comment text |
| * @exception java.lang.Exception |
| */ |
| public void callComment(int data) throws Exception; |
| } |
| |
| /** |
| * Constructor |
| */ |
| public XMLDocumentScanner(StringPool stringPool, |
| XMLErrorReporter errorReporter, |
| XMLEntityHandler entityHandler, |
| XMLEntityHandler.CharBuffer literalData) { |
| fStringPool = stringPool; |
| fErrorReporter = errorReporter; |
| fEntityHandler = entityHandler; |
| fLiteralData = literalData; |
| fDispatcher = new XMLDeclDispatcher(); |
| fAttrList = new XMLAttrList(fStringPool); |
| } |
| |
| /** |
| * Set the event handler |
| * |
| * @param eventHandler The place to send our callbacks. |
| */ |
| public void setEventHandler(XMLDocumentScanner.EventHandler eventHandler) { |
| fEventHandler = eventHandler; |
| } |
| |
| /** Set the DTD handler. */ |
| public void setDTDHandler(XMLDocumentHandler.DTDHandler dtdHandler) { |
| fDTDHandler = dtdHandler; |
| } |
| |
| /** Sets the grammar resolver. */ |
| public void setGrammarResolver(GrammarResolver resolver) { |
| fGrammarResolver = resolver; |
| } |
| |
| /** |
| * reset the parser so that the instance can be reused |
| * |
| * @param stringPool the string pool instance to be used by the reset parser |
| */ |
| public void reset(StringPool stringPool, XMLEntityHandler.CharBuffer literalData) { |
| fStringPool = stringPool; |
| fLiteralData = literalData; |
| fParseTextDecl = false; |
| fSeenRootElement = false; |
| fSeenDoctypeDecl = false; |
| fStandalone = false; |
| fScanningDTD = false; |
| fDispatcher = new XMLDeclDispatcher(); |
| fScannerState = SCANNER_STATE_XML_DECL; |
| fScannerMarkupDepth = 0; |
| fAttrList = new XMLAttrList(fStringPool); |
| } |
| |
| // |
| // From the standard: |
| // |
| // [1] document ::= prolog element Misc* |
| // |
| // [22] prolog ::= XMLDecl? Misc* (doctypedecl Misc*)? |
| // [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' |
| // [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ") |
| // |
| // The beginning of XMLDecl simplifies to: |
| // '<?xml' S ... |
| // |
| // [27] Misc ::= Comment | PI | S |
| // [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->' |
| // [16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>' |
| // [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) |
| // |
| // [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? |
| // ('[' (markupdecl | PEReference | S)* ']' S?)? '>' |
| // |
| /** |
| * Entry point for parsing |
| * |
| * @param doItAll if true the entire document is parsed otherwise just |
| * the next segment of the document is parsed |
| */ |
| public boolean parseSome(boolean doItAll) throws Exception |
| { |
| do { |
| if (!fDispatcher.dispatch(doItAll)) |
| return false; |
| } while (doItAll); |
| return true; |
| } |
| |
| /** |
| * Change readers |
| * |
| * @param nextReader the new reader that the scanner will use |
| * @param nextReaderId id of the reader to change to |
| * @exception throws java.lang.Exception |
| */ |
| public void readerChange(XMLEntityHandler.EntityReader nextReader, int nextReaderId) throws Exception { |
| fEntityReader = nextReader; |
| fReaderId = nextReaderId; |
| if (fScannerState == SCANNER_STATE_ATTRIBUTE_VALUE) { |
| fAttValueOffset = fEntityReader.currentOffset(); |
| fAttValueMark = fAttValueOffset; |
| } |
| |
| //also propagate the change to DTDScanner if there is one |
| if (fDTDScanner != null && fScanningDTD) |
| fDTDScanner.readerChange(nextReader, nextReaderId); |
| } |
| |
| /** |
| * Handle the end of input |
| * |
| * @param entityName the handle in the string pool of the name of the entity which has reached end of input |
| * @param moreToFollow if true, there is still input left to process in other readers |
| * @exception java.lang.Exception |
| */ |
| public void endOfInput(int entityName, boolean moreToFollow) throws Exception { |
| if (fDTDScanner != null && fScanningDTD){ |
| fDTDScanner.endOfInput(entityName, moreToFollow); |
| } |
| fDispatcher.endOfInput(entityName, moreToFollow); |
| } |
| |
| /** |
| * Tell if scanner has reached end of input |
| * @return true if scanner has reached end of input. |
| */ |
| public boolean atEndOfInput() { |
| return fScannerState == SCANNER_STATE_END_OF_INPUT; |
| } |
| |
| // |
| // [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'" |
| // |
| /** |
| * Scan an attribute value |
| * |
| * @param elementType handle to the element whose attribute value is being scanned |
| * @param attrName handle in the string pool of the name of attribute being scanned |
| * @param asSymbol controls whether the value is a string (duplicates allowed) or a symbol (duplicates not allowed) |
| * @return handle in the string pool of the scanned value |
| * @exception java.lang.Exception |
| */ |
| public int scanAttValue(QName element, QName attribute, boolean asSymbol) throws Exception { |
| boolean single; |
| if (!(single = fEntityReader.lookingAtChar('\'', true)) && !fEntityReader.lookingAtChar('\"', true)) { |
| reportFatalXMLError(XMLMessages.MSG_QUOTE_REQUIRED_IN_ATTVALUE, |
| XMLMessages.P10_QUOTE_REQUIRED, |
| element.rawname, |
| attribute.rawname); |
| return -1; |
| } |
| char qchar = single ? '\'' : '\"'; |
| fAttValueMark = fEntityReader.currentOffset(); |
| int attValue = fEntityReader.scanAttValue(qchar, asSymbol); |
| if (attValue >= 0) |
| return attValue; |
| int previousState = setScannerState(SCANNER_STATE_ATTRIBUTE_VALUE); |
| fAttValueReader = fReaderId; |
| // REVISIT: What should this be? |
| fAttValueElementType = element.rawname; |
| // REVISIT: What should this be? |
| fAttValueAttrName = attribute.rawname; |
| fAttValueOffset = fEntityReader.currentOffset(); |
| int dataOffset = fLiteralData.length(); |
| if (fAttValueOffset - fAttValueMark > 0) |
| fEntityReader.append(fLiteralData, fAttValueMark, fAttValueOffset - fAttValueMark); |
| fAttValueMark = fAttValueOffset; |
| boolean setMark = false; |
| boolean skippedCR; |
| while (true) { |
| if (fEntityReader.lookingAtChar(qchar, true)) { |
| if (fReaderId == fAttValueReader) |
| break; |
| } else if (fEntityReader.lookingAtChar(' ', true)) { |
| // |
| // no action required |
| // |
| } else if ((skippedCR = fEntityReader.lookingAtChar((char)0x0D, true)) || fEntityReader.lookingAtSpace(true)) { |
| if (fAttValueOffset - fAttValueMark > 0) |
| fEntityReader.append(fLiteralData, fAttValueMark, fAttValueOffset - fAttValueMark); |
| setMark = true; |
| fLiteralData.append(' '); |
| if (skippedCR) { |
| // |
| // REVISIT - HACK !!! code changed to pass incorrect OASIS test 'valid-sa-110' |
| // Uncomment the next line to conform to the spec... |
| // |
| //fEntityReader.lookingAtChar((char)0x0A, true); |
| } |
| } else if (fEntityReader.lookingAtChar('&', true)) { |
| if (fAttValueOffset - fAttValueMark > 0) |
| fEntityReader.append(fLiteralData, fAttValueMark, fAttValueOffset - fAttValueMark); |
| setMark = true; |
| // |
| // Check for character reference first. |
| // |
| if (fEntityReader.lookingAtChar('#', true)) { |
| int ch = scanCharRef(); |
| if (ch != -1) { |
| if (ch < 0x10000) |
| fLiteralData.append((char)ch); |
| else { |
| fLiteralData.append((char)(((ch-0x00010000)>>10)+0xd800)); |
| fLiteralData.append((char)(((ch-0x00010000)&0x3ff)+0xdc00)); |
| } |
| } |
| } else { |
| // |
| // Entity reference |
| // |
| int nameOffset = fEntityReader.currentOffset(); |
| fEntityReader.skipPastName(';'); |
| int nameLength = fEntityReader.currentOffset() - nameOffset; |
| if (nameLength == 0) { |
| reportFatalXMLError(XMLMessages.MSG_NAME_REQUIRED_IN_REFERENCE, |
| XMLMessages.P68_NAME_REQUIRED); |
| } else if (!fEntityReader.lookingAtChar(';', true)) { |
| reportFatalXMLError(XMLMessages.MSG_SEMICOLON_REQUIRED_IN_REFERENCE, |
| XMLMessages.P68_SEMICOLON_REQUIRED, |
| fEntityReader.addString(nameOffset, nameLength)); |
| } else { |
| int entityName = fEntityReader.addSymbol(nameOffset, nameLength); |
| fEntityHandler.startReadingFromEntity(entityName, fScannerMarkupDepth, XMLEntityHandler.ENTITYREF_IN_ATTVALUE); |
| } |
| } |
| } else if (fEntityReader.lookingAtChar('<', true)) { |
| if (fAttValueOffset - fAttValueMark > 0) |
| fEntityReader.append(fLiteralData, fAttValueMark, fAttValueOffset - fAttValueMark); |
| setMark = true; |
| reportFatalXMLError(XMLMessages.MSG_LESSTHAN_IN_ATTVALUE, |
| XMLMessages.WFC_NO_LESSTHAN_IN_ATTVALUE, |
| element.rawname, |
| attribute.rawname); |
| } else if (!fEntityReader.lookingAtValidChar(true)) { |
| if (fAttValueOffset - fAttValueMark > 0) |
| fEntityReader.append(fLiteralData, fAttValueMark, fAttValueOffset - fAttValueMark); |
| setMark = true; |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState == SCANNER_STATE_END_OF_INPUT) |
| return -1; |
| if (invChar >= 0) { |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_ATTVALUE, |
| XMLMessages.P10_INVALID_CHARACTER, |
| fStringPool.toString(element.rawname), |
| fStringPool.toString(attribute.rawname), |
| Integer.toHexString(invChar)); |
| } |
| } |
| fAttValueOffset = fEntityReader.currentOffset(); |
| if (setMark) { |
| fAttValueMark = fAttValueOffset; |
| setMark = false; |
| } |
| } |
| restoreScannerState(previousState); |
| int dataLength = fLiteralData.length() - dataOffset; |
| if (dataLength == 0) { |
| return fEntityReader.addString(fAttValueMark, fAttValueOffset - fAttValueMark); |
| } |
| if (fAttValueOffset - fAttValueMark > 0) { |
| fEntityReader.append(fLiteralData, fAttValueMark, fAttValueOffset - fAttValueMark); |
| dataLength = fLiteralData.length() - dataOffset; |
| } |
| int value = fLiteralData.addString(dataOffset, dataLength); |
| return value; |
| } |
| |
| /** |
| * Check the value of an XML Language attribute |
| * @param langValue the handle in the string pool of the value to be checked |
| * @exception java.lang.Exception |
| */ |
| public void checkXMLLangAttributeValue(int langValue) throws Exception { |
| String lang = fStringPool.toString(langValue); |
| int offset = -1; |
| if (lang.length() >= 2) { |
| char ch0 = lang.charAt(0); |
| if (lang.charAt(1) == '-') { |
| if (ch0 == 'i' || ch0 == 'I' || ch0 == 'x' || ch0 == 'X') { |
| offset = 1; |
| } |
| } else { |
| char ch1 = lang.charAt(1); |
| if (((ch0 >= 'a' && ch0 <= 'z') || (ch0 >= 'A' && ch0 <= 'Z')) && |
| ((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z'))) { |
| offset = 2; |
| } |
| } |
| } |
| if (offset > 0 && lang.length() > offset) { |
| char ch = lang.charAt(offset++); |
| if (ch != '-') { |
| offset = -1; |
| } else { |
| while (true) { |
| if (ch == '-') { |
| if (lang.length() == offset) { |
| offset = -1; |
| break; |
| } |
| ch = lang.charAt(offset++); |
| if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { |
| offset = -1; |
| break; |
| } |
| if (lang.length() == offset) |
| break; |
| } else if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { |
| offset = -1; |
| break; |
| } else if (lang.length() == offset) |
| break; |
| ch = lang.charAt(offset++); |
| } |
| } |
| } |
| if (offset == -1) { |
| reportFatalXMLError(XMLMessages.MSG_XML_LANG_INVALID, |
| XMLMessages.P33_INVALID, |
| lang); |
| } |
| } |
| |
| // |
| // |
| // |
| void reportFatalXMLError(int majorCode, int minorCode) throws Exception { |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| majorCode, |
| minorCode, |
| null, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| } |
| void reportFatalXMLError(int majorCode, int minorCode, int stringIndex1) throws Exception { |
| Object[] args = { fStringPool.toString(stringIndex1) }; |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| majorCode, |
| minorCode, |
| args, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| } |
| void reportFatalXMLError(int majorCode, int minorCode, String string1) throws Exception { |
| Object[] args = { string1 }; |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| majorCode, |
| minorCode, |
| args, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| } |
| void reportFatalXMLError(int majorCode, int minorCode, int stringIndex1, int stringIndex2) throws Exception { |
| Object[] args = { fStringPool.toString(stringIndex1), |
| fStringPool.toString(stringIndex2) }; |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| majorCode, |
| minorCode, |
| args, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| } |
| void reportFatalXMLError(int majorCode, int minorCode, String string1, String string2) throws Exception { |
| Object[] args = { string1, string2 }; |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| majorCode, |
| minorCode, |
| args, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| } |
| void reportFatalXMLError(int majorCode, int minorCode, String string1, String string2, String string3) throws Exception { |
| Object[] args = { string1, string2, string3 }; |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| majorCode, |
| minorCode, |
| args, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| } |
| void abortMarkup(int majorCode, int minorCode) throws Exception { |
| reportFatalXMLError(majorCode, minorCode); |
| skipPastEndOfCurrentMarkup(); |
| } |
| void abortMarkup(int majorCode, int minorCode, int stringIndex1) throws Exception { |
| reportFatalXMLError(majorCode, minorCode, stringIndex1); |
| skipPastEndOfCurrentMarkup(); |
| } |
| void abortMarkup(int majorCode, int minorCode, String string1) throws Exception { |
| reportFatalXMLError(majorCode, minorCode, string1); |
| skipPastEndOfCurrentMarkup(); |
| } |
| void abortMarkup(int majorCode, int minorCode, int stringIndex1, int stringIndex2) throws Exception { |
| reportFatalXMLError(majorCode, minorCode, stringIndex1, stringIndex2); |
| skipPastEndOfCurrentMarkup(); |
| } |
| void skipPastEndOfCurrentMarkup() throws Exception { |
| fEntityReader.skipToChar('>'); |
| if (fEntityReader.lookingAtChar('>', true)) |
| fScannerMarkupDepth--; |
| } |
| // |
| // |
| // |
| int setScannerState(int state) { |
| int oldState = fScannerState; |
| fScannerState = state; |
| return oldState; |
| } |
| void restoreScannerState(int state) { |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) |
| fScannerState = state; |
| } |
| // |
| // |
| // |
| /** |
| * The main loop of the scanner is implemented by calling the dispatch method |
| * of ScannerDispatcher with a flag which tells the dispatcher whether to continue |
| * or return. The scanner logic is split up into dispatchers for various syntatic |
| * components of XML. //REVISIT more rationale needed |
| */ |
| interface ScannerDispatcher { |
| /** |
| * scan an XML syntactic component |
| * |
| * @param keepgoing if true continue on to the next dispatcher, otherwise return |
| * @return true if scanning was successful //REVISIT - does it ever return false or does it just throw? |
| * @exception java.lang.Exception |
| */ |
| boolean dispatch(boolean keepgoing) throws Exception; |
| /** |
| * endOfInput encapsulates the end of entity handling for each dispatcher |
| * |
| * @param entityName StringPool handle of the entity that has reached the end |
| * @param moreToFollow true if there is more input to be read |
| * @exception |
| */ |
| void endOfInput(int entityName, boolean moreToFollow) throws Exception; |
| } |
| final class XMLDeclDispatcher implements ScannerDispatcher { |
| public boolean dispatch(boolean keepgoing) throws Exception { |
| fEventHandler.callStartDocument(); |
| if (fEntityReader.lookingAtChar('<', true)) { |
| fScannerMarkupDepth++; |
| setScannerState(SCANNER_STATE_START_OF_MARKUP); |
| if (fEntityReader.lookingAtChar('?', true)) { |
| int piTarget = fEntityReader.scanName(' '); |
| if (piTarget == -1) { |
| abortMarkup(XMLMessages.MSG_PITARGET_REQUIRED, |
| XMLMessages.P16_PITARGET_REQUIRED); |
| } else if ("xml".equals(fStringPool.toString(piTarget))) { |
| if (fEntityReader.lookingAtSpace(true)) { // an XMLDecl looks like a PI with the target 'xml' |
| scanXMLDeclOrTextDecl(false); |
| } else { // a PI target matching 'xml' |
| abortMarkup(XMLMessages.MSG_RESERVED_PITARGET, |
| XMLMessages.P17_RESERVED_PITARGET); |
| } |
| } else { // PI |
| scanPI(piTarget); |
| } |
| fDispatcher = new PrologDispatcher(); |
| restoreScannerState(SCANNER_STATE_PROLOG); |
| return true; |
| } |
| if (fEntityReader.lookingAtChar('!', true)) { |
| if (fEntityReader.lookingAtChar('-', true)) { // comment ? |
| if (fEntityReader.lookingAtChar('-', true)) { |
| scanComment(); // scan through the closing '-->' |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_PROLOG, |
| XMLMessages.P22_NOT_RECOGNIZED); |
| } |
| } else { |
| if (fEntityReader.skippedString(doctype_string)) { |
| setScannerState(SCANNER_STATE_DOCTYPE); |
| fSeenDoctypeDecl = true; |
| scanDoctypeDecl(fStandalone); // scan through the closing '>' |
| fScannerMarkupDepth--; |
| fDispatcher = new PrologDispatcher(); |
| restoreScannerState(SCANNER_STATE_PROLOG); |
| return true; |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_PROLOG, |
| XMLMessages.P22_NOT_RECOGNIZED); |
| } |
| } |
| } else { |
| fDispatcher = new ContentDispatcher(); |
| restoreScannerState(SCANNER_STATE_ROOT_ELEMENT); |
| return true; |
| } |
| } else { |
| if (fEntityReader.lookingAtSpace(true)) { |
| fEntityReader.skipPastSpaces(); |
| } else if (!fEntityReader.lookingAtValidChar(false)) { |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (invChar >= 0) { |
| String arg = Integer.toHexString(invChar); |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_PROLOG, |
| XMLMessages.P22_INVALID_CHARACTER, |
| arg); |
| } |
| } |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_PROLOG, |
| XMLMessages.P22_NOT_RECOGNIZED); |
| fEntityReader.lookingAtValidChar(true); |
| } |
| } |
| fDispatcher = new PrologDispatcher(); |
| restoreScannerState(SCANNER_STATE_PROLOG); |
| return true; |
| } |
| public void endOfInput(int entityName, boolean moreToFollow) throws Exception { |
| switch (fScannerState) { |
| case SCANNER_STATE_XML_DECL: |
| case SCANNER_STATE_START_OF_MARKUP: |
| case SCANNER_STATE_DOCTYPE: |
| break; |
| case SCANNER_STATE_COMMENT: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_COMMENT_UNTERMINATED, |
| XMLMessages.P15_UNTERMINATED); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_COMMENT_NOT_IN_ONE_ENTITY, |
| XMLMessages.P78_NOT_WELLFORMED); |
| } |
| break; |
| case SCANNER_STATE_PI: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_PI_UNTERMINATED, |
| XMLMessages.P16_UNTERMINATED); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_PI_NOT_IN_ONE_ENTITY, |
| XMLMessages.P78_NOT_WELLFORMED); |
| } |
| break; |
| default: |
| throw new RuntimeException("FWK001 1] ScannerState="+fScannerState+"\n" + "1\t"+fScannerState); |
| } |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_ROOT_ELEMENT_REQUIRED, |
| XMLMessages.P1_ELEMENT_REQUIRED); |
| fDispatcher = new EndOfInputDispatcher(); |
| setScannerState(SCANNER_STATE_END_OF_INPUT); |
| } |
| } |
| } |
| final class PrologDispatcher implements ScannerDispatcher { |
| public boolean dispatch(boolean keepgoing) throws Exception { |
| do { |
| if (fEntityReader.lookingAtChar('<', true)) { |
| fScannerMarkupDepth++; |
| setScannerState(SCANNER_STATE_START_OF_MARKUP); |
| if (fEntityReader.lookingAtChar('?', true)) { |
| int piTarget = fEntityReader.scanName(' '); |
| if (piTarget == -1) { |
| abortMarkup(XMLMessages.MSG_PITARGET_REQUIRED, |
| XMLMessages.P16_PITARGET_REQUIRED); |
| } else if ("xml".equals(fStringPool.toString(piTarget))) { |
| if (fEntityReader.lookingAtSpace(true)) { // an XMLDecl looks like a PI with the target 'xml' |
| abortMarkup(XMLMessages.MSG_XMLDECL_MUST_BE_FIRST, |
| XMLMessages.P22_XMLDECL_MUST_BE_FIRST); |
| } else { // a PI target matching 'xml' |
| abortMarkup(XMLMessages.MSG_RESERVED_PITARGET, |
| XMLMessages.P17_RESERVED_PITARGET); |
| } |
| } else { // PI |
| scanPI(piTarget); |
| } |
| } else if (fEntityReader.lookingAtChar('!', true)) { |
| if (fEntityReader.lookingAtChar('-', true)) { // comment ? |
| if (fEntityReader.lookingAtChar('-', true)) { |
| scanComment(); // scan through the closing '-->' |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_PROLOG, |
| XMLMessages.P22_NOT_RECOGNIZED); |
| } |
| } else { |
| if (!fSeenDoctypeDecl && fEntityReader.skippedString(doctype_string)) { |
| setScannerState(SCANNER_STATE_DOCTYPE); |
| fSeenDoctypeDecl = true; |
| scanDoctypeDecl(fStandalone); // scan through the closing '>' |
| fScannerMarkupDepth--; |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_PROLOG, |
| XMLMessages.P22_NOT_RECOGNIZED); |
| } |
| } |
| } else { |
| fDispatcher = new ContentDispatcher(); |
| restoreScannerState(SCANNER_STATE_ROOT_ELEMENT); |
| return true; |
| } |
| restoreScannerState(SCANNER_STATE_PROLOG); |
| } else if (fEntityReader.lookingAtSpace(true)) { |
| fEntityReader.skipPastSpaces(); |
| } else if (!fEntityReader.lookingAtValidChar(false)) { |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (invChar >= 0) { |
| String arg = Integer.toHexString(invChar); |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_PROLOG, |
| XMLMessages.P22_INVALID_CHARACTER, |
| arg); |
| } |
| } |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_PROLOG, |
| XMLMessages.P22_NOT_RECOGNIZED); |
| fEntityReader.lookingAtValidChar(true); |
| } |
| } while (fScannerState != SCANNER_STATE_END_OF_INPUT && keepgoing); |
| return true; |
| } |
| public void endOfInput(int entityName, boolean moreToFollow) throws Exception { |
| switch (fScannerState) { |
| case SCANNER_STATE_PROLOG: |
| case SCANNER_STATE_START_OF_MARKUP: |
| case SCANNER_STATE_DOCTYPE: |
| break; |
| case SCANNER_STATE_COMMENT: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_COMMENT_UNTERMINATED, |
| XMLMessages.P15_UNTERMINATED); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_COMMENT_NOT_IN_ONE_ENTITY, |
| XMLMessages.P78_NOT_WELLFORMED); |
| } |
| break; |
| case SCANNER_STATE_PI: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_PI_UNTERMINATED, |
| XMLMessages.P16_UNTERMINATED); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_PI_NOT_IN_ONE_ENTITY, |
| XMLMessages.P78_NOT_WELLFORMED); |
| } |
| break; |
| default: |
| throw new RuntimeException("FWK001 2] ScannerState="+fScannerState+"\n" + "2\t"+fScannerState); |
| } |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_ROOT_ELEMENT_REQUIRED, |
| XMLMessages.P1_ELEMENT_REQUIRED); |
| fDispatcher = new EndOfInputDispatcher(); |
| setScannerState(SCANNER_STATE_END_OF_INPUT); |
| } |
| } |
| } |
| int fCurrentElementType = -1; |
| public int getCurrentElementType() { |
| return fCurrentElementType; |
| } |
| final class ContentDispatcher implements ScannerDispatcher { |
| private int fContentReader = -1; |
| private int fElementDepth = 0; |
| private int[] fElementTypeStack = new int[8]; |
| |
| void popElementType() { |
| if (fElementDepth-- == 0) { |
| throw new RuntimeException("FWK002 popElementType: fElementDepth-- == 0."); |
| } |
| if (fElementDepth == 0) { |
| fCurrentElementType = - 1; |
| } else { |
| fCurrentElementType = fElementTypeStack[fElementDepth - 1]; |
| } |
| } |
| |
| public boolean dispatch(boolean keepgoing) throws Exception { |
| do { |
| switch (fScannerState) { |
| case SCANNER_STATE_ROOT_ELEMENT: |
| { |
| scanElementType(fEntityReader, '>', fElementQName); |
| if (fElementQName.rawname != -1) { |
| // |
| // root element |
| // |
| fContentReader = fReaderId; |
| fSeenRootElement = true; |
| // |
| // scan element |
| // |
| if (fEntityReader.lookingAtChar('>', true)) { |
| // |
| // we have more content |
| // |
| fEventHandler.callStartElement(fElementQName); |
| fScannerMarkupDepth--; |
| if (fElementDepth == fElementTypeStack.length) { |
| int[] newStack = new int[fElementDepth * 2]; |
| System.arraycopy(fElementTypeStack, 0, newStack, 0, fElementDepth); |
| fElementTypeStack = newStack; |
| } |
| fCurrentElementType = fElementQName.rawname; |
| fElementTypeStack[fElementDepth] = fElementQName.rawname; |
| fElementDepth++; |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| } else if (scanElement(fElementQName)) { |
| // |
| // we have more content |
| // |
| if (fElementDepth == fElementTypeStack.length) { |
| int[] newStack = new int[fElementDepth * 2]; |
| System.arraycopy(fElementTypeStack, 0, newStack, 0, fElementDepth); |
| fElementTypeStack = newStack; |
| } |
| fCurrentElementType = fElementQName.rawname; |
| fElementTypeStack[fElementDepth] = fElementQName.rawname; |
| fElementDepth++; |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| } else { |
| fDispatcher = new TrailingMiscDispatcher(); |
| restoreScannerState(SCANNER_STATE_TRAILING_MISC); |
| return true; |
| } |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_PROLOG, |
| XMLMessages.P22_NOT_RECOGNIZED); |
| fDispatcher = new PrologDispatcher(); |
| restoreScannerState(SCANNER_STATE_PROLOG); |
| return true; |
| } |
| break; |
| } |
| case SCANNER_STATE_START_OF_MARKUP: |
| if (fEntityReader.lookingAtChar('?', true)) { |
| int piTarget = fEntityReader.scanName(' '); |
| if (piTarget == -1) { |
| abortMarkup(XMLMessages.MSG_PITARGET_REQUIRED, |
| XMLMessages.P16_PITARGET_REQUIRED); |
| } else if ("xml".equals(fStringPool.toString(piTarget))) { |
| if (fEntityReader.lookingAtSpace(true)) { // an XMLDecl looks like a PI with the target 'xml' |
| if (fParseTextDecl) { |
| scanXMLDeclOrTextDecl(true); |
| fParseTextDecl = false; |
| } else { |
| abortMarkup(XMLMessages.MSG_TEXTDECL_MUST_BE_FIRST, |
| XMLMessages.P30_TEXTDECL_MUST_BE_FIRST); |
| } |
| } else { // a PI target matching 'xml' |
| abortMarkup(XMLMessages.MSG_RESERVED_PITARGET, |
| XMLMessages.P17_RESERVED_PITARGET); |
| } |
| } else { // PI |
| scanPI(piTarget); |
| } |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| } else if (fEntityReader.lookingAtChar('!', true)) { |
| if (fEntityReader.lookingAtChar('-', true)) { // comment ? |
| if (fEntityReader.lookingAtChar('-', true)) { |
| scanComment(); // scan through the closing '-->' |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_CONTENT, |
| XMLMessages.P43_NOT_RECOGNIZED); |
| } |
| } else { |
| if (fEntityReader.skippedString(cdata_string)) { |
| fEntityReader.setInCDSect(true); |
| fEventHandler.callStartCDATA(); |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_CONTENT, |
| XMLMessages.P43_NOT_RECOGNIZED); |
| } |
| } |
| } else { |
| if (fEntityReader.lookingAtChar('/', true)) { |
| // |
| // [42] ETag ::= '</' Name S? '>' |
| // |
| if (!scanExpectedElementType(fEntityReader, '>', fCurrentElementType)) { |
| abortMarkup(XMLMessages.MSG_ETAG_REQUIRED, |
| XMLMessages.P39_UNTERMINATED, |
| fCurrentElementType); |
| } else { |
| if (!fEntityReader.lookingAtChar('>', true)) { |
| fEntityReader.skipPastSpaces(); |
| if (!fEntityReader.lookingAtChar('>', true)) { |
| reportFatalXMLError(XMLMessages.MSG_ETAG_UNTERMINATED, |
| XMLMessages.P42_UNTERMINATED, |
| fCurrentElementType); |
| } |
| } |
| fScannerMarkupDepth--; |
| fEventHandler.callEndElement(fReaderId); |
| if (fElementDepth-- == 0) { |
| throw new RuntimeException("FWK002 popElementType: fElementDepth-- == 0."); |
| } |
| if (fElementDepth == 0) { |
| fCurrentElementType = - 1; |
| fDispatcher = new TrailingMiscDispatcher(); |
| restoreScannerState(SCANNER_STATE_TRAILING_MISC); |
| return true; |
| } else { |
| fCurrentElementType = fElementTypeStack[fElementDepth - 1]; |
| } |
| } |
| } else { |
| scanElementType(fEntityReader, '>', fElementQName); |
| if (fElementQName.rawname != -1) { |
| // |
| // element |
| // |
| if (fEntityReader.lookingAtChar('>', true)) { |
| fEventHandler.callStartElement(fElementQName); |
| fScannerMarkupDepth--; |
| if (fElementDepth == fElementTypeStack.length) { |
| int[] newStack = new int[fElementDepth * 2]; |
| System.arraycopy(fElementTypeStack, 0, newStack, 0, fElementDepth); |
| fElementTypeStack = newStack; |
| } |
| fCurrentElementType = fElementQName.rawname; |
| fElementTypeStack[fElementDepth] = fElementQName.rawname; |
| fElementDepth++; |
| } else { |
| if (scanElement(fElementQName)) { |
| if (fElementDepth == fElementTypeStack.length) { |
| int[] newStack = new int[fElementDepth * 2]; |
| System.arraycopy(fElementTypeStack, 0, newStack, 0, fElementDepth); |
| fElementTypeStack = newStack; |
| } |
| fCurrentElementType = fElementQName.rawname; |
| fElementTypeStack[fElementDepth] = fElementQName.rawname; |
| fElementDepth++; |
| } |
| } |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_CONTENT, |
| XMLMessages.P43_NOT_RECOGNIZED); |
| } |
| } |
| } |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| break; |
| case SCANNER_STATE_CONTENT: |
| if (fParseTextDecl && fEntityReader.lookingAtChar('<', true)) { |
| fScannerMarkupDepth++; |
| setScannerState(SCANNER_STATE_START_OF_MARKUP); |
| continue; |
| } |
| // REVISIT: Is this the right thing to do? Do we need to |
| // save more information on the stack? |
| fCurrentElementQName.setValues(-1, -1, fCurrentElementType); |
| switch (fEntityReader.scanContent(fCurrentElementQName)) { |
| case XMLEntityHandler.CONTENT_RESULT_START_OF_PI: |
| fScannerMarkupDepth++; |
| int piTarget = fEntityReader.scanName(' '); |
| if (piTarget == -1) { |
| abortMarkup(XMLMessages.MSG_PITARGET_REQUIRED, |
| XMLMessages.P16_PITARGET_REQUIRED); |
| } else if ("xml".equals(fStringPool.toString(piTarget))) { |
| if (fEntityReader.lookingAtSpace(true)) { // an XMLDecl looks like a PI with the target 'xml' |
| if (fReaderId == fContentReader) { |
| abortMarkup(XMLMessages.MSG_XMLDECL_MUST_BE_FIRST, |
| XMLMessages.P22_XMLDECL_MUST_BE_FIRST); |
| } else { |
| abortMarkup(XMLMessages.MSG_TEXTDECL_MUST_BE_FIRST, |
| XMLMessages.P30_TEXTDECL_MUST_BE_FIRST); |
| } |
| } else { // a PI target matching 'xml' |
| abortMarkup(XMLMessages.MSG_RESERVED_PITARGET, |
| XMLMessages.P17_RESERVED_PITARGET); |
| } |
| } else { // PI |
| scanPI(piTarget); |
| } |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_START_OF_COMMENT: |
| fScannerMarkupDepth++; |
| fParseTextDecl = false; |
| scanComment(); // scan through the closing '-->' |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_START_OF_CDSECT: |
| fScannerMarkupDepth++; |
| fParseTextDecl = false; |
| fEntityReader.setInCDSect(true); |
| fEventHandler.callStartCDATA(); |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_START_OF_ETAG: |
| fScannerMarkupDepth++; |
| fParseTextDecl = false; |
| // |
| // [42] ETag ::= '</' Name S? '>' |
| // |
| if (!scanExpectedElementType(fEntityReader, '>', fCurrentElementType)) { |
| abortMarkup(XMLMessages.MSG_ETAG_REQUIRED, |
| XMLMessages.P39_UNTERMINATED, |
| fCurrentElementType); |
| } else { |
| if (!fEntityReader.lookingAtChar('>', true)) { |
| fEntityReader.skipPastSpaces(); |
| if (!fEntityReader.lookingAtChar('>', true)) { |
| reportFatalXMLError(XMLMessages.MSG_ETAG_UNTERMINATED, |
| XMLMessages.P42_UNTERMINATED, |
| fCurrentElementType); |
| } |
| } |
| fScannerMarkupDepth--; |
| fEventHandler.callEndElement(fReaderId); |
| if (fElementDepth-- == 0) { |
| throw new RuntimeException("FWK002 popElementType: fElementDepth-- == 0."); |
| } |
| if (fElementDepth == 0) { |
| fCurrentElementType = - 1; |
| fDispatcher = new TrailingMiscDispatcher(); |
| restoreScannerState(SCANNER_STATE_TRAILING_MISC); |
| return true; |
| } else { |
| fCurrentElementType = fElementTypeStack[fElementDepth - 1]; |
| } |
| } |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_START_OF_ELEMENT: |
| { |
| fScannerMarkupDepth++; |
| fParseTextDecl = false; |
| scanElementType(fEntityReader, '>', fElementQName); |
| if (fElementQName.rawname != -1) { |
| if (fEntityReader.lookingAtChar('>', true)) { |
| fEventHandler.callStartElement(fElementQName); |
| fScannerMarkupDepth--; |
| if (fElementDepth == fElementTypeStack.length) { |
| int[] newStack = new int[fElementDepth * 2]; |
| System.arraycopy(fElementTypeStack, 0, newStack, 0, fElementDepth); |
| fElementTypeStack = newStack; |
| } |
| fCurrentElementType = fElementQName.rawname; |
| fElementTypeStack[fElementDepth] = fElementQName.rawname; |
| fElementDepth++; |
| } else { |
| if (scanElement(fElementQName)) { |
| if (fElementDepth == fElementTypeStack.length) { |
| int[] newStack = new int[fElementDepth * 2]; |
| System.arraycopy(fElementTypeStack, 0, newStack, 0, fElementDepth); |
| fElementTypeStack = newStack; |
| } |
| fCurrentElementType = fElementQName.rawname; |
| fElementTypeStack[fElementDepth] = fElementQName.rawname; |
| fElementDepth++; |
| } |
| } |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_CONTENT, |
| XMLMessages.P43_NOT_RECOGNIZED); |
| } |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) |
| fScannerState = SCANNER_STATE_CONTENT; |
| break; |
| } |
| case XMLEntityHandler.CONTENT_RESULT_MATCHING_ETAG: |
| { |
| fParseTextDecl = false; |
| fEventHandler.callEndElement(fReaderId); |
| if (fElementDepth-- == 0) { |
| throw new RuntimeException("FWK002 popElementType: fElementDepth-- == 0."); |
| } |
| if (fElementDepth == 0) { |
| fCurrentElementType = - 1; |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| fDispatcher = new TrailingMiscDispatcher(); |
| fScannerState = SCANNER_STATE_TRAILING_MISC; |
| } |
| return true; |
| } else { |
| fCurrentElementType = fElementTypeStack[fElementDepth - 1]; |
| } |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) |
| fScannerState = SCANNER_STATE_CONTENT; |
| break; |
| } |
| case XMLEntityHandler.CONTENT_RESULT_START_OF_CHARREF: |
| fParseTextDecl = false; |
| // |
| // [67] Reference ::= EntityRef | CharRef |
| // [68] EntityRef ::= '&' Name ';' |
| // [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' |
| // |
| setScannerState(SCANNER_STATE_REFERENCE); |
| int num = scanCharRef(); |
| // if (num == -1) num = 0xfffd; // REVISIT - alternative is to use Unicode replacement char |
| if (num != -1) |
| fEventHandler.callCharacters(num); |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_REFERENCE_END_OF_INPUT: |
| // REVISIT - This should hopefully get us the "reference not |
| // contained in one entity" error when endOfInput is called. |
| // Test that this is so... |
| // |
| // fall through... |
| // |
| case XMLEntityHandler.CONTENT_RESULT_START_OF_ENTITYREF: |
| fParseTextDecl = false; |
| // |
| // [68] EntityRef ::= '&' Name ';' |
| // |
| setScannerState(SCANNER_STATE_REFERENCE); |
| int nameOffset = fEntityReader.currentOffset(); |
| fEntityReader.skipPastName(';'); |
| int nameLength = fEntityReader.currentOffset() - nameOffset; |
| if (nameLength == 0) { |
| reportFatalXMLError(XMLMessages.MSG_NAME_REQUIRED_IN_REFERENCE, |
| XMLMessages.P68_NAME_REQUIRED); |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| } else if (!fEntityReader.lookingAtChar(';', true)) { |
| reportFatalXMLError(XMLMessages.MSG_SEMICOLON_REQUIRED_IN_REFERENCE, |
| XMLMessages.P68_SEMICOLON_REQUIRED, |
| fEntityReader.addString(nameOffset, nameLength)); |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| } else { |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| int entityName = fEntityReader.addSymbol(nameOffset, nameLength); |
| fParseTextDecl = fEntityHandler.startReadingFromEntity(entityName, fElementDepth, XMLEntityHandler.ENTITYREF_IN_CONTENT); |
| } |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_END_OF_CDSECT: |
| fParseTextDecl = false; |
| // |
| // [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) |
| // [21] CDEnd ::= ']]>' |
| // |
| if (fEntityReader.getInCDSect()) { |
| fEntityReader.setInCDSect(false); |
| fEventHandler.callEndCDATA(); |
| fScannerMarkupDepth--; |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_CDEND_IN_CONTENT, |
| XMLMessages.P14_INVALID); |
| } |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_INVALID_CHAR: |
| fParseTextDecl = false; |
| // |
| // The reader will also use this state if it |
| // encounters the end of input while reading |
| // content. We need to check for this case. |
| // |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (!fEntityReader.lookingAtValidChar(false)) { |
| // |
| // [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] // any Unicode character, excluding the |
| // | [#xE000-#xFFFD] | [#x10000-#x10FFFF] // surrogate blocks, FFFE, and FFFF. |
| // |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (invChar >= 0) { |
| if (fEntityReader.getInCDSect()) { |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_CDSECT, |
| XMLMessages.P20_INVALID_CHARACTER, |
| Integer.toHexString(invChar)); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_CONTENT, |
| XMLMessages.P43_INVALID_CHARACTER, |
| Integer.toHexString(invChar)); |
| } |
| } |
| } |
| } |
| restoreScannerState(SCANNER_STATE_CONTENT); |
| } |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_MARKUP_NOT_RECOGNIZED: |
| fParseTextDecl = false; |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_CONTENT, |
| XMLMessages.P43_NOT_RECOGNIZED); |
| break; |
| case XMLEntityHandler.CONTENT_RESULT_MARKUP_END_OF_INPUT: |
| // REVISIT - This should hopefully get us the "markup not |
| // contained in one entity" error when endOfInput is called. |
| // Test that this is so... |
| fScannerMarkupDepth++; |
| fParseTextDecl = false; |
| fScannerState = SCANNER_STATE_START_OF_MARKUP; |
| break; |
| default: |
| throw new RuntimeException("FWK001 3] ScannerState="+fScannerState+"\n" + "3\t"+fScannerState); // should not happen |
| } |
| break; |
| default: |
| throw new RuntimeException("FWK001 4] ScannerState="+fScannerState+"\n" + "4\t"+fScannerState); |
| } |
| } while (fScannerState != SCANNER_STATE_END_OF_INPUT && keepgoing); |
| return true; |
| } |
| public void endOfInput(int entityName, boolean moreToFollow) throws Exception { |
| switch (fScannerState) { |
| case SCANNER_STATE_ROOT_ELEMENT: |
| case SCANNER_STATE_START_OF_MARKUP: |
| break; |
| case SCANNER_STATE_CONTENT: |
| if (fEntityReader.getInCDSect()) { |
| reportFatalXMLError(XMLMessages.MSG_CDSECT_UNTERMINATED, |
| XMLMessages.P18_UNTERMINATED); |
| } |
| break; |
| case SCANNER_STATE_ATTRIBUTE_LIST: |
| if (!moreToFollow) { |
| // REVISIT reportFatalXMLError(XMLMessages.MSG_TAG1); |
| } else { |
| // REVISIT reportFatalXMLError(XMLMessages.MSG_TAG1); |
| } |
| break; |
| case SCANNER_STATE_ATTRIBUTE_NAME: |
| if (!moreToFollow) { |
| // REVISIT reportFatalXMLError(XMLMessages.MSG_ATTVAL0); |
| } else { |
| // REVISIT reportFatalXMLError(XMLMessages.MSG_ATTVAL0); |
| } |
| break; |
| case SCANNER_STATE_ATTRIBUTE_VALUE: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_ATTRIBUTE_VALUE_UNTERMINATED, |
| XMLMessages.P10_UNTERMINATED, |
| fAttValueElementType, |
| fAttValueAttrName); |
| } else if (fReaderId == fAttValueReader) { |
| // REVISIT reportFatalXMLError(XMLMessages.MSG_ATTVAL0); |
| } else { |
| fEntityReader.append(fLiteralData, fAttValueMark, fAttValueOffset - fAttValueMark); |
| } |
| break; |
| case SCANNER_STATE_COMMENT: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_COMMENT_UNTERMINATED, |
| XMLMessages.P15_UNTERMINATED); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_COMMENT_NOT_IN_ONE_ENTITY, |
| XMLMessages.P78_NOT_WELLFORMED); |
| } |
| break; |
| case SCANNER_STATE_PI: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_PI_UNTERMINATED, |
| XMLMessages.P16_UNTERMINATED); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_PI_NOT_IN_ONE_ENTITY, |
| XMLMessages.P78_NOT_WELLFORMED); |
| } |
| break; |
| case SCANNER_STATE_REFERENCE: |
| if (!moreToFollow) { |
| reportFatalXMLError(XMLMessages.MSG_REFERENCE_UNTERMINATED, |
| XMLMessages.P67_UNTERMINATED); |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_REFERENCE_NOT_IN_ONE_ENTITY, |
| XMLMessages.P78_NOT_WELLFORMED); |
| } |
| break; |
| default: |
| throw new RuntimeException("FWK001 5] ScannerState="+fScannerState+"\n" + "5\t"+fScannerState); |
| } |
| if (!moreToFollow) { |
| if (fElementDepth > 0) |
| reportFatalXMLError(XMLMessages.MSG_ETAG_REQUIRED, |
| XMLMessages.P39_UNTERMINATED, |
| fCurrentElementType); |
| fDispatcher = new EndOfInputDispatcher(); |
| setScannerState(SCANNER_STATE_END_OF_INPUT); |
| } |
| } |
| } |
| final class TrailingMiscDispatcher implements ScannerDispatcher { |
| public boolean dispatch(boolean keepgoing) throws Exception { |
| do { |
| if (fEntityReader.lookingAtChar('<', true)) { |
| fScannerMarkupDepth++; |
| setScannerState(SCANNER_STATE_START_OF_MARKUP); |
| if (fEntityReader.lookingAtChar('?', true)) { |
| int piTarget = fEntityReader.scanName(' '); |
| if (piTarget == -1) { |
| abortMarkup(XMLMessages.MSG_PITARGET_REQUIRED, |
| XMLMessages.P16_PITARGET_REQUIRED); |
| } else if ("xml".equals(fStringPool.toString(piTarget))) { |
| if (fEntityReader.lookingAtSpace(true)) { // an XMLDecl looks like a PI with the target 'xml' |
| abortMarkup(XMLMessages.MSG_XMLDECL_MUST_BE_FIRST, |
| XMLMessages.P22_XMLDECL_MUST_BE_FIRST); |
| } else { // a PI target matching 'xml' |
| abortMarkup(XMLMessages.MSG_RESERVED_PITARGET, |
| XMLMessages.P17_RESERVED_PITARGET); |
| } |
| } else { // PI |
| scanPI(piTarget); |
| } |
| } else if (fEntityReader.lookingAtChar('!', true)) { |
| if (fEntityReader.lookingAtChar('-', true) && |
| fEntityReader.lookingAtChar('-', true)) { // comment ? |
| scanComment(); // scan through the closing '-->' |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_MISC, |
| XMLMessages.P27_NOT_RECOGNIZED); |
| } |
| } else { |
| abortMarkup(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_MISC, |
| XMLMessages.P27_NOT_RECOGNIZED); |
| } |
| restoreScannerState(SCANNER_STATE_TRAILING_MISC); |
| } else if (fEntityReader.lookingAtSpace(true)) { |
| fEntityReader.skipPastSpaces(); |
| } else if (!fEntityReader.lookingAtValidChar(false)) { |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (invChar >= 0) { |
| String arg = Integer.toHexString(invChar); |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_MISC, |
| XMLMessages.P27_INVALID_CHARACTER, |
| arg); |
| } |
| } |
| } else { |
| reportFatalXMLError(XMLMessages.MSG_MARKUP_NOT_RECOGNIZED_IN_MISC, |
| XMLMessages.P27_NOT_RECOGNIZED); |
| fEntityReader.lookingAtValidChar(true); |
| } |
| } while (fScannerState != SCANNER_STATE_END_OF_INPUT && keepgoing); |
| return true; |
| } |
| public void endOfInput(int entityName, boolean moreToFollow) throws Exception { |
| if (moreToFollow) |
| throw new RuntimeException("FWK003 TrailingMiscDispatcher.endOfInput moreToFollow"); |
| switch (fScannerState) { |
| case SCANNER_STATE_TRAILING_MISC: |
| case SCANNER_STATE_START_OF_MARKUP: |
| break; |
| case SCANNER_STATE_COMMENT: |
| reportFatalXMLError(XMLMessages.MSG_COMMENT_UNTERMINATED, |
| XMLMessages.P15_UNTERMINATED); |
| break; |
| case SCANNER_STATE_PI: |
| reportFatalXMLError(XMLMessages.MSG_PI_UNTERMINATED, |
| XMLMessages.P16_UNTERMINATED); |
| break; |
| default: |
| throw new RuntimeException("FWK001 6] ScannerState="+fScannerState+"\n" + "6\t"+fScannerState); |
| } |
| fDispatcher = new EndOfInputDispatcher(); |
| setScannerState(SCANNER_STATE_END_OF_INPUT); |
| } |
| } |
| final class EndOfInputDispatcher implements ScannerDispatcher { |
| public boolean dispatch(boolean keepgoing) throws Exception { |
| if (fScannerState != SCANNER_STATE_TERMINATED) |
| fEventHandler.callEndDocument(); |
| setScannerState(SCANNER_STATE_TERMINATED); |
| return false; |
| } |
| public void endOfInput(int entityName, boolean moreToFollow) throws Exception { |
| throw new RuntimeException("FWK001 7] ScannerState="+fScannerState+"\n" + "7\t"+fScannerState); |
| } |
| } |
| // |
| // From the standard: |
| // |
| // [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' |
| // [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ") |
| // [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" ) |
| // [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* |
| // [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") |
| // | ('"' ('yes' | 'no') '"')) |
| // |
| // [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>' |
| // |
| void scanXMLDeclOrTextDecl(boolean scanningTextDecl) throws Exception |
| { |
| int version = -1; |
| int encoding = -1; |
| int standalone = -1; |
| final int XMLDECL_START = 0; |
| final int XMLDECL_VERSION = 1; |
| final int XMLDECL_ENCODING = 2; |
| final int XMLDECL_STANDALONE = 3; |
| final int XMLDECL_FINISHED = 4; |
| int state = XMLDECL_START; |
| do { |
| fEntityReader.skipPastSpaces(); |
| int offset = fEntityReader.currentOffset(); |
| if (scanningTextDecl) { |
| if (state == XMLDECL_START && fEntityReader.skippedString(version_string)) { |
| state = XMLDECL_VERSION; |
| } else if (fEntityReader.skippedString(encoding_string)) { |
| state = XMLDECL_ENCODING; |
| } else { |
| abortMarkup(XMLMessages.MSG_ENCODINGDECL_REQUIRED, |
| XMLMessages.P77_ENCODINGDECL_REQUIRED); |
| return; |
| } |
| } else { |
| if (state == XMLDECL_START) { |
| if (!fEntityReader.skippedString(version_string)) { |
| abortMarkup(XMLMessages.MSG_VERSIONINFO_REQUIRED, |
| XMLMessages.P23_VERSIONINFO_REQUIRED); |
| return; |
| } |
| state = XMLDECL_VERSION; |
| } else { |
| if (state == XMLDECL_VERSION) { |
| if (fEntityReader.skippedString(encoding_string)) |
| state = XMLDECL_ENCODING; |
| else |
| state = XMLDECL_STANDALONE; |
| } else |
| state = XMLDECL_STANDALONE; |
| if (state == XMLDECL_STANDALONE && !fEntityReader.skippedString(standalone_string)) |
| break; |
| } |
| } |
| int length = fEntityReader.currentOffset() - offset; |
| fEntityReader.skipPastSpaces(); |
| if (!fEntityReader.lookingAtChar('=', true)) { |
| int majorCode = scanningTextDecl ? |
| XMLMessages.MSG_EQ_REQUIRED_IN_TEXTDECL : |
| XMLMessages.MSG_EQ_REQUIRED_IN_XMLDECL; |
| int minorCode = state == XMLDECL_VERSION ? |
| XMLMessages.P24_EQ_REQUIRED : |
| (state == XMLDECL_ENCODING ? |
| XMLMessages.P80_EQ_REQUIRED : |
| XMLMessages.P32_EQ_REQUIRED); |
| abortMarkup(majorCode, minorCode, fEntityReader.addString(offset, length)); |
| return; |
| } |
| fEntityReader.skipPastSpaces(); |
| int result = fEntityReader.scanStringLiteral(); |
| switch (result) { |
| case XMLEntityHandler.STRINGLIT_RESULT_QUOTE_REQUIRED: |
| { |
| int majorCode = scanningTextDecl ? |
| XMLMessages.MSG_QUOTE_REQUIRED_IN_TEXTDECL : |
| XMLMessages.MSG_QUOTE_REQUIRED_IN_XMLDECL; |
| int minorCode = state == XMLDECL_VERSION ? |
| XMLMessages.P24_QUOTE_REQUIRED : |
| (state == XMLDECL_ENCODING ? |
| XMLMessages.P80_QUOTE_REQUIRED : |
| XMLMessages.P32_QUOTE_REQUIRED); |
| abortMarkup(majorCode, minorCode, fEntityReader.addString(offset, length)); |
| return; |
| } |
| case XMLEntityHandler.STRINGLIT_RESULT_INVALID_CHAR: |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (invChar >= 0) { |
| int majorCode = scanningTextDecl ? |
| XMLMessages.MSG_INVALID_CHAR_IN_TEXTDECL : |
| XMLMessages.MSG_INVALID_CHAR_IN_XMLDECL; |
| int minorCode = state == XMLDECL_VERSION ? |
| XMLMessages.P26_INVALID_CHARACTER : |
| (state == XMLDECL_ENCODING ? |
| XMLMessages.P81_INVALID_CHARACTER : |
| XMLMessages.P32_INVALID_CHARACTER); |
| reportFatalXMLError(majorCode, minorCode, Integer.toHexString(invChar)); |
| } |
| skipPastEndOfCurrentMarkup(); |
| } |
| return; |
| default: |
| break; |
| } |
| switch (state) { |
| case XMLDECL_VERSION: |
| // |
| // version="..." |
| // |
| version = result; |
| String versionString = fStringPool.toString(version); |
| if (!"1.0".equals(versionString)) { |
| if (!validVersionNum(versionString)) { |
| abortMarkup(XMLMessages.MSG_VERSIONINFO_INVALID, |
| XMLMessages.P26_INVALID_VALUE, |
| versionString); |
| return; |
| } |
| // NOTE: RECOVERABLE ERROR |
| Object[] args = { versionString }; |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| XMLMessages.MSG_VERSION_NOT_SUPPORTED, |
| XMLMessages.P26_NOT_SUPPORTED, |
| args, |
| XMLErrorReporter.ERRORTYPE_RECOVERABLE_ERROR); |
| // REVISIT - hope it is compatible... |
| // skipPastEndOfCurrentMarkup(); |
| // return; |
| } |
| if (!fEntityReader.lookingAtSpace(true)) { |
| if (scanningTextDecl) { |
| abortMarkup(XMLMessages.MSG_SPACE_REQUIRED_IN_TEXTDECL, |
| XMLMessages.P80_WHITESPACE_REQUIRED); |
| return; |
| } |
| state = XMLDECL_FINISHED; |
| } |
| break; |
| case XMLDECL_ENCODING: |
| // |
| // encoding = "..." |
| // |
| encoding = result; |
| String encodingString = fStringPool.toString(encoding); |
| if (!validEncName(encodingString)) { |
| abortMarkup(XMLMessages.MSG_ENCODINGDECL_INVALID, |
| XMLMessages.P81_INVALID_VALUE, |
| encodingString); |
| return; |
| } |
| if (!fEntityReader.lookingAtSpace(true)) { |
| state = XMLDECL_FINISHED; |
| } else if (scanningTextDecl) { |
| fEntityReader.skipPastSpaces(); |
| state = XMLDECL_FINISHED; |
| } |
| break; |
| case XMLDECL_STANDALONE: |
| // |
| // standalone="..." |
| // |
| standalone = result; |
| String standaloneString = fStringPool.toString(standalone); |
| boolean yes = "yes".equals(standaloneString); |
| if (!yes && !"no".equals(standaloneString)) { |
| abortMarkup(XMLMessages.MSG_SDDECL_INVALID, |
| XMLMessages.P32_INVALID_VALUE, |
| standaloneString); |
| return; |
| } |
| fStandalone = yes; |
| fEntityReader.skipPastSpaces(); |
| state = XMLDECL_FINISHED; |
| break; |
| } |
| } while (state != XMLDECL_FINISHED); |
| if (!fEntityReader.lookingAtChar('?', true) || !fEntityReader.lookingAtChar('>', true)) { |
| int majorCode, minorCode; |
| if (scanningTextDecl) { |
| majorCode = XMLMessages.MSG_TEXTDECL_UNTERMINATED; |
| minorCode = XMLMessages.P77_UNTERMINATED; |
| } else { |
| majorCode = XMLMessages.MSG_XMLDECL_UNTERMINATED; |
| minorCode = XMLMessages.P23_UNTERMINATED; |
| } |
| abortMarkup(majorCode, minorCode); |
| return; |
| } |
| fScannerMarkupDepth--; |
| if (scanningTextDecl) { |
| fEventHandler.callTextDecl(version, encoding); |
| } else { |
| // |
| // Now that we have hit '?>' we are done with XML decl. Call the |
| // handler before returning. |
| // |
| fEventHandler.callXMLDecl(version, encoding, standalone); |
| // if we see standalone = 'yes', call the eventHandler - XMLValidator |
| if (fStandalone) { |
| fEventHandler.callStandaloneIsYes(); |
| } |
| } |
| } |
| // |
| // From the standard: |
| // |
| // [39] element ::= EmptyElemTag | STag content ETag |
| // [44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>' |
| // [40] STag ::= '<' Name (S Attribute)* S? '>' |
| // [41] Attribute ::= Name Eq AttValue |
| // [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'" |
| // [67] Reference ::= EntityRef | CharRef |
| // [68] EntityRef ::= '&' Name ';' |
| // [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' |
| // [43] content ::= (element | CharData | Reference | CDSect | PI | Comment)* |
| // [42] ETag ::= '</' Name S? '>' |
| // |
| // Note: We have already scanned Name. |
| // |
| boolean scanElement(QName element) throws Exception |
| { |
| // |
| // Scan for attributes |
| // |
| boolean greater = false; |
| boolean slash = false; |
| if (greater = fEntityReader.lookingAtChar('>', true)) { |
| // no attributes |
| } else if (fEntityReader.lookingAtSpace(true)) { |
| int previousState = setScannerState(SCANNER_STATE_ATTRIBUTE_LIST); |
| while (true) { |
| fEntityReader.skipPastSpaces(); |
| // |
| // [41] Attribute ::= Name Eq AttValue |
| // |
| if ((greater = fEntityReader.lookingAtChar('>', true)) || (slash = fEntityReader.lookingAtChar('/', true))) |
| break; |
| // |
| // Name |
| // |
| setScannerState(SCANNER_STATE_ATTRIBUTE_NAME); |
| scanAttributeName(fEntityReader, element, fAttributeQName); |
| if (fAttributeQName.rawname == -1) { |
| break; |
| } |
| // |
| // Eq |
| // |
| fEntityReader.skipPastSpaces(); |
| if (!fEntityReader.lookingAtChar('=', true)) { |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| abortMarkup(XMLMessages.MSG_EQ_REQUIRED_IN_ATTRIBUTE, |
| XMLMessages.P41_EQ_REQUIRED, |
| element.rawname, fAttributeQName.rawname); |
| restoreScannerState(previousState); |
| } |
| return false; |
| } |
| fEntityReader.skipPastSpaces(); |
| int result = scanAttValue(element, fAttributeQName, false); |
| if (result == RESULT_FAILURE) { |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| skipPastEndOfCurrentMarkup(); |
| restoreScannerState(previousState); |
| } |
| return false; |
| } else if (result == RESULT_DUPLICATE_ATTR) { |
| reportFatalXMLError(XMLMessages.MSG_ATTRIBUTE_NOT_UNIQUE, |
| XMLMessages.WFC_UNIQUE_ATT_SPEC, |
| element.rawname, fAttributeQName.rawname); |
| } |
| //The validator will check whether we have a duplicate attr in the start tag. |
| if ( fEventHandler.attribute(element, fAttributeQName, result) ) { |
| reportFatalXMLError(XMLMessages.MSG_ATTRIBUTE_NOT_UNIQUE, |
| XMLMessages.WFC_UNIQUE_ATT_SPEC, |
| element.rawname, fAttributeQName.rawname); |
| } |
| restoreScannerState(SCANNER_STATE_ATTRIBUTE_LIST); |
| if (!fEntityReader.lookingAtSpace(true)) { |
| if (!(greater = fEntityReader.lookingAtChar('>', true))) |
| slash = fEntityReader.lookingAtChar('/', true); |
| break; |
| } |
| } |
| restoreScannerState(previousState); |
| } else { |
| slash = fEntityReader.lookingAtChar('/', true); |
| } |
| if (!greater && (!slash || !fEntityReader.lookingAtChar('>', true))) { // '>' or '/>' |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| abortMarkup(XMLMessages.MSG_ELEMENT_UNTERMINATED, |
| XMLMessages.P40_UNTERMINATED, |
| element.rawname); |
| } |
| return false; |
| } |
| fEventHandler.callStartElement(element); |
| fScannerMarkupDepth--; |
| if (slash) { // '/>' |
| fEventHandler.callEndElement(fReaderId); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| // |
| // [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' |
| // |
| int scanCharRef() throws Exception { |
| int valueOffset = fEntityReader.currentOffset(); |
| boolean hex = fEntityReader.lookingAtChar('x', true); |
| int num = fEntityReader.scanCharRef(hex); |
| if (num < 0) { |
| switch (num) { |
| case XMLEntityHandler.CHARREF_RESULT_SEMICOLON_REQUIRED: |
| reportFatalXMLError(XMLMessages.MSG_SEMICOLON_REQUIRED_IN_CHARREF, |
| XMLMessages.P66_SEMICOLON_REQUIRED); |
| return -1; |
| case XMLEntityHandler.CHARREF_RESULT_INVALID_CHAR: |
| int majorCode = hex ? XMLMessages.MSG_HEXDIGIT_REQUIRED_IN_CHARREF : |
| XMLMessages.MSG_DIGIT_REQUIRED_IN_CHARREF; |
| int minorCode = hex ? XMLMessages.P66_HEXDIGIT_REQUIRED : |
| XMLMessages.P66_DIGIT_REQUIRED; |
| reportFatalXMLError(majorCode, minorCode); |
| return -1; |
| case XMLEntityHandler.CHARREF_RESULT_OUT_OF_RANGE: |
| num = 0x110000; // this will cause the right error to be reported below... |
| break; |
| } |
| } |
| // |
| // [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] // any Unicode character, excluding the |
| // | [#xE000-#xFFFD] | [#x10000-#x10FFFF] // surrogate blocks, FFFE, and FFFF. |
| // |
| if (num < 0x20) { |
| if (num == 0x09 || num == 0x0A || num == 0x0D) { |
| return num; |
| } |
| } else if (num <= 0xD7FF || (num >= 0xE000 && (num <= 0xFFFD || (num >= 0x10000 && num <= 0x10FFFF)))) { |
| return num; |
| } |
| int valueLength = fEntityReader.currentOffset() - valueOffset; |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHARREF, |
| XMLMessages.WFC_LEGAL_CHARACTER, |
| fEntityReader.addString(valueOffset, valueLength)); |
| return -1; |
| } |
| // |
| // From the standard: |
| // |
| // [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->' |
| // |
| // Called after scanning past '<!--' |
| // |
| void scanComment() throws Exception |
| { |
| int commentOffset = fEntityReader.currentOffset(); |
| boolean sawDashDash = false; |
| int previousState = setScannerState(SCANNER_STATE_COMMENT); |
| while (fScannerState == SCANNER_STATE_COMMENT) { |
| if (fEntityReader.lookingAtChar('-', false)) { |
| int nextEndOffset = fEntityReader.currentOffset(); |
| int endOffset = 0; |
| fEntityReader.lookingAtChar('-', true); |
| int offset = fEntityReader.currentOffset(); |
| int count = 1; |
| while (fEntityReader.lookingAtChar('-', true)) { |
| count++; |
| endOffset = nextEndOffset; |
| nextEndOffset = offset; |
| offset = fEntityReader.currentOffset(); |
| } |
| if (count > 1) { |
| if (fEntityReader.lookingAtChar('>', true)) { |
| if (!sawDashDash && count > 2) { |
| reportFatalXMLError(XMLMessages.MSG_DASH_DASH_IN_COMMENT, |
| XMLMessages.P15_DASH_DASH); |
| sawDashDash = true; |
| } |
| fScannerMarkupDepth--; |
| fEventHandler.callComment(fEntityReader.addString(commentOffset, endOffset - commentOffset)); |
| restoreScannerState(previousState); |
| return; |
| } else if (!sawDashDash) { |
| reportFatalXMLError(XMLMessages.MSG_DASH_DASH_IN_COMMENT, |
| XMLMessages.P15_DASH_DASH); |
| sawDashDash = true; |
| } |
| } |
| } else { |
| if (!fEntityReader.lookingAtValidChar(true)) { |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (invChar >= 0) { |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_COMMENT, |
| XMLMessages.P15_INVALID_CHARACTER, |
| Integer.toHexString(invChar)); |
| } |
| } |
| } |
| } |
| } |
| restoreScannerState(previousState); |
| } |
| // |
| // From the standard: |
| // |
| // [16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>' |
| // [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) |
| // |
| void scanPI(int piTarget) throws Exception |
| { |
| String piTargetString = fStringPool.toString(piTarget); |
| if (piTargetString.length() == 3 && |
| (piTargetString.charAt(0) == 'X' || piTargetString.charAt(0) == 'x') && |
| (piTargetString.charAt(1) == 'M' || piTargetString.charAt(1) == 'm') && |
| (piTargetString.charAt(2) == 'L' || piTargetString.charAt(2) == 'l')) { |
| abortMarkup(XMLMessages.MSG_RESERVED_PITARGET, |
| XMLMessages.P17_RESERVED_PITARGET); |
| return; |
| } |
| int prevState = setScannerState(SCANNER_STATE_PI); |
| int piDataOffset = -1; |
| int piDataLength = -1; |
| if (!fEntityReader.lookingAtSpace(true)) { |
| if (!fEntityReader.lookingAtChar('?', true) || !fEntityReader.lookingAtChar('>', true)) { |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| abortMarkup(XMLMessages.MSG_SPACE_REQUIRED_IN_PI, |
| XMLMessages.P16_WHITESPACE_REQUIRED); |
| restoreScannerState(prevState); |
| } |
| return; |
| } |
| piDataLength = 0; |
| } else { |
| fEntityReader.skipPastSpaces(); |
| piDataOffset = fEntityReader.currentOffset(); |
| while (fScannerState == SCANNER_STATE_PI) { |
| while (fEntityReader.lookingAtChar('?', false)) { |
| int offset = fEntityReader.currentOffset(); |
| fEntityReader.lookingAtChar('?', true); |
| if (fEntityReader.lookingAtChar('>', true)) { |
| piDataLength = offset - piDataOffset; |
| break; |
| } |
| } |
| if (piDataLength >= 0) |
| break; |
| if (!fEntityReader.lookingAtValidChar(true)) { |
| int invChar = fEntityReader.scanInvalidChar(); |
| if (fScannerState != SCANNER_STATE_END_OF_INPUT) { |
| if (invChar >= 0) { |
| reportFatalXMLError(XMLMessages.MSG_INVALID_CHAR_IN_PI, |
| XMLMessages.P16_INVALID_CHARACTER, |
| Integer.toHexString(invChar)); |
| } |
| skipPastEndOfCurrentMarkup(); |
| restoreScannerState(prevState); |
| } |
| return; |
| } |
| } |
| } |
| fScannerMarkupDepth--; |
| restoreScannerState(prevState); |
| int piData = piDataLength == 0 ? |
| StringPool.EMPTY_STRING : fEntityReader.addString(piDataOffset, piDataLength); |
| fEventHandler.callProcessingInstruction(piTarget, piData); |
| } |
| |
| /** Sets whether the parser preprocesses namespaces. */ |
| public void setNamespacesEnabled(boolean enabled) { |
| fNamespacesEnabled = enabled; |
| } |
| |
| /** Returns whether the parser processes namespaces. */ |
| public boolean getNamespacesEnabled() { |
| return fNamespacesEnabled; |
| } |
| |
| /** Sets whether the parser validates. */ |
| public void setValidationEnabled(boolean enabled) { |
| fValidationEnabled = enabled; |
| if (fDTDScanner != null) { |
| fDTDScanner.setValidationEnabled(enabled); |
| } |
| } |
| |
| /** Returns true if validation is turned on. */ |
| public boolean getValidationEnabled() { |
| return fValidationEnabled; |
| } |
| |
| // old EventHandler methods pushed back into scanner |
| |
| /** Scans element type. */ |
| private void scanElementType(XMLEntityHandler.EntityReader entityReader, |
| char fastchar, QName element) throws Exception { |
| |
| if (!fNamespacesEnabled) { |
| element.clear(); |
| element.localpart = entityReader.scanName(fastchar); |
| element.rawname = element.localpart; |
| } |
| else { |
| entityReader.scanQName(fastchar, element); |
| if (entityReader.lookingAtChar(':', false)) { |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| XMLMessages.MSG_TWO_COLONS_IN_QNAME, |
| XMLMessages.P5_INVALID_CHARACTER, |
| null, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| entityReader.skipPastNmtoken(' '); |
| } |
| } |
| |
| fEventHandler.element(element); |
| |
| } // scanElementType(XMLEntityHandler.EntityReader,char,QName) |
| |
| /** Scans expected element type. */ |
| private boolean scanExpectedElementType(XMLEntityHandler.EntityReader entityReader, |
| char fastchar, int elementType) |
| throws Exception { |
| |
| /***/ |
| // REVISIT: Why aren't we using the 'element' parameter? -Ac |
| // REVISIT: I replaced the 'fCurrentElement' with 'element' parameter, still working, |
| // just wondering Why are we using CharArrayRange in the first place? -ericye |
| if (fCurrentElementCharArrayRange == null) { |
| fCurrentElementCharArrayRange = fStringPool.createCharArrayRange(); |
| } |
| fStringPool.getCharArrayRange(elementType, fCurrentElementCharArrayRange); |
| return entityReader.scanExpectedName(fastchar, fCurrentElementCharArrayRange); |
| /*** |
| entityReader.scanQName(fastchar, element); |
| return true; |
| /***/ |
| |
| } // scanExpectedElementType(XMLEntityHandler.EntityReader,char,QName) |
| |
| /** Scans attribute name. */ |
| private void scanAttributeName(XMLEntityHandler.EntityReader entityReader, |
| QName element, QName attribute) |
| throws Exception { |
| |
| /*** |
| // REVISIT: What's this check for? |
| if (!fSeenRootElement) { |
| fSeenRootElement = true; |
| rootElementSpecified(element); |
| fStringPool.resetShuffleCount(); |
| } |
| /***/ |
| |
| if (!fNamespacesEnabled) { |
| attribute.clear(); |
| attribute.localpart = entityReader.scanName('='); |
| attribute.rawname = attribute.localpart; |
| } |
| else { |
| entityReader.scanQName('=', attribute); |
| if (entityReader.lookingAtChar(':', false)) { |
| fErrorReporter.reportError(fErrorReporter.getLocator(), |
| XMLMessages.XML_DOMAIN, |
| XMLMessages.MSG_TWO_COLONS_IN_QNAME, |
| XMLMessages.P5_INVALID_CHARACTER, |
| null, |
| XMLErrorReporter.ERRORTYPE_FATAL_ERROR); |
| entityReader.skipPastNmtoken(' '); |
| } |
| } |
| |
| } // scanAttributeName(XMLEntityHandler.EntityReader,QName,QName) |
| |
| /** Scan doctype declaration. */ |
| private void scanDoctypeDecl(boolean standalone) throws Exception { |
| |
| fScanningDTD = true; |
| |
| /*** |
| fScanningDTD = true; |
| fCheckedForSchema = true; |
| /***/ |
| fSeenDoctypeDecl = true; |
| /*** |
| fStandaloneReader = standalone ? fEntityHandler.getReaderId() : -1; |
| fDeclsAreExternal = false; |
| if (fDTDImporter == null) { |
| fDTDImporter = new DTDImporter(fStringPool, fErrorReporter, fEntityHandler, this); |
| } |
| else { |
| fDTDImporter.reset(fStringPool); |
| } |
| fDTDImporter.initHandlers(fDTDHandler); |
| fDTDImporter.setValidating(fValidating); |
| fDTDImporter.setNamespacesEnabled(fNamespacesEnabled); |
| if (fDTDImporter.scanDoctypeDecl(standalone) && fValidating) { |
| // check declared elements |
| if (fWarningOnUndeclaredElements) { |
| // REVISIT: comment out because won't compile |
| // checkDeclaredElements(); |
| } |
| |
| // check required notations |
| fEntityHandler.checkRequiredNotations(); |
| } |
| fScanningDTD = false; |
| /***/ |
| if (fDTDScanner == null) { |
| fDTDScanner = new XMLDTDScanner(fStringPool, fErrorReporter, fEntityHandler, new ChunkyCharArray(fStringPool)); |
| fDTDScanner.setValidationEnabled(fValidationEnabled); |
| fDTDScanner.setNamespacesEnabled(fNamespacesEnabled); |
| } |
| else { |
| fDTDScanner.reset(fStringPool, new ChunkyCharArray(fStringPool)); |
| } |
| fDTDScanner.setDTDHandler(fDTDHandler); |
| fDTDScanner.setGrammarResolver(fGrammarResolver); |
| // REVISIT: What about standalone? |
| if (fDTDScanner.scanDoctypeDecl()) { |
| if (fDTDScanner.getReadingExternalEntity()) { |
| fDTDScanner.scanDecls(true); |
| } |
| // REVISIT: What about validation and checking stuff? |
| } |
| //VC_NOTATION_DECLARED |
| if (fValidationEnabled) { |
| ((DefaultEntityHandler)fEntityHandler).checkRequiredNotations(); |
| } |
| /***/ |
| fScanningDTD = false; |
| |
| } // scanDoctypeDecl(boolean) |
| |
| /** Scan attribute value. */ |
| private int scanAttValue(QName element, QName attribute) throws Exception { |
| |
| //fAttrNameLocator = getLocatorImpl(fAttrNameLocator); |
| int attValue = scanAttValue(element, attribute, fValidationEnabled); |
| if (attValue == -1) { |
| return XMLDocumentScanner.RESULT_FAILURE; |
| } |
| |
| |
| /*** |
| // REVISIT: This is validation related. |
| if (!fValidating && fAttDefCount == 0) { |
| int attType = fCDATASymbol; |
| if (fAttrListHandle == -1) |
| fAttrListHandle = fAttrList.startAttrList(); |
| // REVISIT: Should this be localpart or rawname? |
| if (fAttrList.addAttr(attribute, attValue, attType, true, true) == -1) { |
| return XMLDocumentScanner.RESULT_DUPLICATE_ATTR; |
| } |
| return XMLDocumentScanner.RESULT_SUCCESS; |
| } |
| /****/ |
| |
| /**** |
| // REVISIT: Validation. What should these be? |
| int attDefIndex = getAttDef(element, attribute); |
| if (attDefIndex == -1) { |
| |
| if (fValidating) { |
| // REVISIT - cache the elem/attr tuple so that we only give |
| // this error once for each unique occurrence |
| Object[] args = { fStringPool.toString(element.rawname), |
| fStringPool.toString(attribute.rawname) }; |
| fErrorReporter.reportError(fAttrNameLocator, |
| XMLMessages.XML_DOMAIN, |
| XMLMessages.MSG_ATTRIBUTE_NOT_DECLARED, |
| XMLMessages.VC_ATTRIBUTE_VALUE_TYPE, |
| args, |
| XMLErrorReporter.ERRORTYPE_RECOVERABLE_ERROR); |
| } |
| |
| int attType = fCDATASymbol; |
| if (fAttrListHandle == -1) { |
| fAttrListHandle = fAttrList.startAttrList(); |
| } |
| // REVISIT: Validation. What should the name be? |
| if (fAttrList.addAttr(attribute, attValue, attType, true, true) == -1) { |
| return XMLDocumentScanner.RESULT_DUPLICATE_ATTR; |
| } |
| return XMLDocumentScanner.RESULT_SUCCESS; |
| } |
| /****/ |
| |
| /**** |
| int attType = getAttType(attDefIndex); |
| if (attType != fCDATASymbol) { |
| AttributeValidator av = getAttributeValidator(attDefIndex); |
| int enumHandle = getEnumeration(attDefIndex); |
| // REVISIT: Validation. What should these be? |
| attValue = av.normalize(element, attribute, |
| attValue, attType, enumHandle); |
| } |
| |
| if (fAttrListHandle == -1) { |
| fAttrListHandle = fAttrList.startAttrList(); |
| } |
| // REVISIT: Validation. What should the name be? |
| if (fAttrList.addAttr(attribute, attValue, attType, true, true) == -1) { |
| return XMLDocumentScanner.RESULT_DUPLICATE_ATTR; |
| } |
| /***/ |
| |
| return XMLDocumentScanner.RESULT_SUCCESS; |
| |
| } // scanAttValue(QName,QName):int |
| |
| /** Returns true if the version number is valid. */ |
| private boolean validVersionNum(String version) { |
| return XMLCharacterProperties.validVersionNum(version); |
| } |
| |
| /** Returns true if the encoding name is valid. */ |
| private boolean validEncName(String encoding) { |
| return XMLCharacterProperties.validEncName(encoding); |
| } |
| |
| } // class XMLDocumentScanner |