1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.xerces.impl;
19
20 import java.io.CharConversionException;
21 import java.io.EOFException;
22 import java.io.IOException;
23
24 import org.apache.xerces.impl.io.MalformedByteSequenceException;
25 import org.apache.xerces.impl.msg.XMLMessageFormatter;
26 import org.apache.xerces.util.SymbolTable;
27 import org.apache.xerces.xni.parser.XMLComponentManager;
28 import org.apache.xerces.xni.parser.XMLConfigurationException;
29 import org.apache.xerces.xni.parser.XMLInputSource;
30
31 /**
32 * This class scans the version of the document to determine
33 * which scanner to use: XML 1.1 or XML 1.0.
34 * The version is scanned using XML 1.1. scanner.
35 *
36 * @xerces.internal
37 *
38 * @author Neil Graham, IBM
39 * @author Elena Litani, IBM
40 * @version $Id: XMLVersionDetector.java 572055 2007-09-02 17:55:43Z mrglavas $
41 */
42 public class XMLVersionDetector {
43
44 //
45 // Constants
46 //
47
48 private static final char[] XML11_VERSION = new char[]{'1', '.', '1'};
49
50 // property identifiers
51
52 /** Property identifier: symbol table. */
53 protected static final String SYMBOL_TABLE =
54 Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
55
56 /** Property identifier: error reporter. */
57 protected static final String ERROR_REPORTER =
58 Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
59
60 /** Property identifier: entity manager. */
61 protected static final String ENTITY_MANAGER =
62 Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_MANAGER_PROPERTY;
63
64 //
65 // Data
66 //
67
68 /** Symbol: "version". */
69 protected static final String fVersionSymbol = "version".intern();
70
71 // symbol: [xml]:
72 protected static final String fXMLSymbol = "[xml]".intern();
73
74 /** Symbol table. */
75 protected SymbolTable fSymbolTable;
76
77 /** Error reporter. */
78 protected XMLErrorReporter fErrorReporter;
79
80 /** Entity manager. */
81 protected XMLEntityManager fEntityManager;
82
83 protected String fEncoding = null;
84
85 private final char [] fExpectedVersionString = {'<', '?', 'x', 'm', 'l', ' ', 'v', 'e', 'r', 's',
86 'i', 'o', 'n', '=', ' ', ' ', ' ', ' ', ' '};
87
88 /**
89 *
90 *
91 * @param componentManager The component manager.
92 *
93 * @throws XNIException Throws exception if required features and
94 * properties cannot be found.
95 */
96 public void reset(XMLComponentManager componentManager)
97 throws XMLConfigurationException {
98
99 // Xerces properties
100 fSymbolTable = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE);
101 fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER);
102 fEntityManager = (XMLEntityManager)componentManager.getProperty(ENTITY_MANAGER);
103 for(int i=14; i<fExpectedVersionString.length; i++ )
104 fExpectedVersionString[i] = ' ';
105 } // reset(XMLComponentManager)
106
107 /**
108 * Reset the reference to the appropriate scanner given the version of the
109 * document and start document scanning.
110 * @param scanner - the scanner to use
111 * @param version - the version of the document (XML 1.1 or XML 1.0).
112 */
113 public void startDocumentParsing(XMLEntityHandler scanner, short version){
114
115 if (version == Constants.XML_VERSION_1_0){
116 fEntityManager.setScannerVersion(Constants.XML_VERSION_1_0);
117 }
118 else {
119 fEntityManager.setScannerVersion(Constants.XML_VERSION_1_1);
120 }
121 // Make sure the locator used by the error reporter is the current entity scanner.
122 fErrorReporter.setDocumentLocator(fEntityManager.getEntityScanner());
123
124 // Note: above we reset fEntityScanner in the entity manager, thus in startEntity
125 // in each scanner fEntityScanner field must be reset to reflect the change.
126 //
127 fEntityManager.setEntityHandler(scanner);
128
129 scanner.startEntity(fXMLSymbol, fEntityManager.getCurrentResourceIdentifier(), fEncoding, null);
130 }
131
132
133 /**
134 * This methods scans the XML declaration to find out the version
135 * (and provisional encoding) of the document.
136 * The scanning is doing using XML 1.1 scanner.
137 * @param inputSource
138 * @return short - Constants.XML_VERSION_1_1 if document version 1.1,
139 * otherwise Constants.XML_VERSION_1_0
140 * @throws IOException
141 */
142 public short determineDocVersion(XMLInputSource inputSource) throws IOException {
143 fEncoding = fEntityManager.setupCurrentEntity(fXMLSymbol, inputSource, false, true);
144
145 // Must use XML 1.0 scanner to handle whitespace correctly
146 // in the XML declaration.
147 fEntityManager.setScannerVersion(Constants.XML_VERSION_1_0);
148 XMLEntityScanner scanner = fEntityManager.getEntityScanner();
149 try {
150 if (!scanner.skipString("<?xml")) {
151 // definitely not a well-formed 1.1 doc!
152 return Constants.XML_VERSION_1_0;
153 }
154 if (!scanner.skipDeclSpaces()) {
155 fixupCurrentEntity(fEntityManager, fExpectedVersionString, 5);
156 return Constants.XML_VERSION_1_0;
157 }
158 if (!scanner.skipString("version")) {
159 fixupCurrentEntity(fEntityManager, fExpectedVersionString, 6);
160 return Constants.XML_VERSION_1_0;
161 }
162 scanner.skipDeclSpaces();
163 // Check if the next character is '='. If it is then consume it.
164 if (scanner.peekChar() != '=') {
165 fixupCurrentEntity(fEntityManager, fExpectedVersionString, 13);
166 return Constants.XML_VERSION_1_0;
167 }
168 scanner.scanChar();
169 scanner.skipDeclSpaces();
170 int quoteChar = scanner.scanChar();
171 fExpectedVersionString[14] = (char) quoteChar;
172 for (int versionPos = 0; versionPos < XML11_VERSION.length; versionPos++) {
173 fExpectedVersionString[15 + versionPos] = (char) scanner.scanChar();
174 }
175 // REVISIT: should we check whether this equals quoteChar?
176 fExpectedVersionString[18] = (char) scanner.scanChar();
177 fixupCurrentEntity(fEntityManager, fExpectedVersionString, 19);
178 int matched = 0;
179 for (; matched < XML11_VERSION.length; matched++) {
180 if (fExpectedVersionString[15 + matched] != XML11_VERSION[matched])
181 break;
182 }
183 return (matched == XML11_VERSION.length) ?
184 Constants.XML_VERSION_1_1 :
185 Constants.XML_VERSION_1_0;
186 }
187 // encoding errors
188 catch (MalformedByteSequenceException e) {
189 fErrorReporter.reportError(e.getDomain(), e.getKey(),
190 e.getArguments(), XMLErrorReporter.SEVERITY_FATAL_ERROR, e);
191 return Constants.XML_VERSION_ERROR;
192 }
193 catch (CharConversionException e) {
194 fErrorReporter.reportError(
195 XMLMessageFormatter.XML_DOMAIN,
196 "CharConversionFailure",
197 null,
198 XMLErrorReporter.SEVERITY_FATAL_ERROR, e);
199 return Constants.XML_VERSION_ERROR;
200 }
201 // premature end of file
202 catch (EOFException e) {
203 fErrorReporter.reportError(
204 XMLMessageFormatter.XML_DOMAIN,
205 "PrematureEOF",
206 null,
207 XMLErrorReporter.SEVERITY_FATAL_ERROR);
208 return Constants.XML_VERSION_ERROR;
209 }
210 }
211
212 // This method prepends "length" chars from the char array,
213 // from offset 0, to the manager's fCurrentEntity.ch.
214 private void fixupCurrentEntity(XMLEntityManager manager,
215 char [] scannedChars, int length) {
216 XMLEntityManager.ScannedEntity currentEntity = manager.getCurrentEntity();
217 if(currentEntity.count-currentEntity.position+length > currentEntity.ch.length) {
218 //resize array; this case is hard to imagine...
219 char[] tempCh = currentEntity.ch;
220 currentEntity.ch = new char[length+currentEntity.count-currentEntity.position+1];
221 System.arraycopy(tempCh, 0, currentEntity.ch, 0, tempCh.length);
222 }
223 if(currentEntity.position < length) {
224 // have to move sensitive stuff out of the way...
225 System.arraycopy(currentEntity.ch, currentEntity.position, currentEntity.ch, length, currentEntity.count-currentEntity.position);
226 currentEntity.count += length-currentEntity.position;
227 } else {
228 // have to reintroduce some whitespace so this parses:
229 for(int i=length; i<currentEntity.position; i++)
230 currentEntity.ch[i]=' ';
231 }
232 // prepend contents...
233 System.arraycopy(scannedChars, 0, currentEntity.ch, 0, length);
234 currentEntity.position = 0;
235 currentEntity.baseCharOffset = 0;
236 currentEntity.startPosition = 0;
237 currentEntity.columnNumber = currentEntity.lineNumber = 1;
238 }
239
240 } // class XMLVersionDetector
241