53 |
|
|
54 |
package gnu.xml.aelfred2; |
package gnu.xml.aelfred2; |
55 |
|
|
56 |
|
import gnu.java.security.action.GetPropertyAction; |
57 |
|
|
58 |
import java.io.BufferedInputStream; |
import java.io.BufferedInputStream; |
59 |
import java.io.CharConversionException; |
import java.io.CharConversionException; |
60 |
import java.io.EOFException; |
import java.io.EOFException; |
65 |
import java.io.UnsupportedEncodingException; |
import java.io.UnsupportedEncodingException; |
66 |
import java.net.URL; |
import java.net.URL; |
67 |
import java.net.URLConnection; |
import java.net.URLConnection; |
68 |
|
import java.security.AccessController; |
69 |
|
|
70 |
// maintaining 1.1 compatibility for now ... |
import java.util.Iterator; |
71 |
// Iterator and Hashmap ought to be faster |
import java.util.HashMap; |
72 |
import java.util.Enumeration; |
import java.util.LinkedList; |
|
import java.util.Hashtable; |
|
|
import java.util.Stack; |
|
73 |
|
|
74 |
import org.xml.sax.InputSource; |
import org.xml.sax.InputSource; |
75 |
import org.xml.sax.SAXException; |
import org.xml.sax.SAXException; |
87 |
*/ |
*/ |
88 |
final class XmlParser |
final class XmlParser |
89 |
{ |
{ |
|
// avoid slow per-character readCh() |
|
|
private final static boolean USE_CHEATS = true; |
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
// Constructors. |
|
|
//////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
/** |
|
|
* Construct a new parser with no associated handler. |
|
|
* @see #setHandler |
|
|
* @see #parse |
|
|
*/ |
|
|
// package private |
|
|
XmlParser () |
|
|
{ |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Set the handler that will receive parsing events. |
|
|
* @param handler The handler to receive callback events. |
|
|
* @see #parse |
|
|
*/ |
|
|
// package private |
|
|
void setHandler (SAXDriver handler) |
|
|
{ |
|
|
this.handler = handler; |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Parse an XML document from the character stream, byte stream, or URI |
|
|
* that you provide (in that order of preference). Any URI that you |
|
|
* supply will become the base URI for resolving relative URI, and may |
|
|
* be used to acquire a reader or byte stream. |
|
|
* |
|
|
* <p> Only one thread at a time may use this parser; since it is |
|
|
* private to this package, post-parse cleanup is done by the caller, |
|
|
* which MUST NOT REUSE the parser (just null it). |
|
|
* |
|
|
* @param systemId Absolute URI of the document; should never be null, |
|
|
* but may be so iff a reader <em>or</em> a stream is provided. |
|
|
* @param publicId The public identifier of the document, or null. |
|
|
* @param reader A character stream; must be null if stream isn't. |
|
|
* @param stream A byte input stream; must be null if reader isn't. |
|
|
* @param encoding The suggested encoding, or null if unknown. |
|
|
* @exception java.lang.Exception Basically SAXException or IOException |
|
|
*/ |
|
|
// package private |
|
|
void doParse ( |
|
|
String systemId, |
|
|
String publicId, |
|
|
Reader reader, |
|
|
InputStream stream, |
|
|
String encoding |
|
|
) throws Exception |
|
|
{ |
|
|
if (handler == null) |
|
|
throw new IllegalStateException ("no callback handler"); |
|
|
|
|
|
initializeVariables (); |
|
|
|
|
|
// predeclare the built-in entities here (replacement texts) |
|
|
// we don't need to intern(), since we're guaranteed literals |
|
|
// are always (globally) interned. |
|
|
setInternalEntity ("amp", "&"); |
|
|
setInternalEntity ("lt", "<"); |
|
|
setInternalEntity ("gt", ">"); |
|
|
setInternalEntity ("apos", "'"); |
|
|
setInternalEntity ("quot", """); |
|
|
|
|
|
try { |
|
|
// pushURL first to ensure locator is correct in startDocument |
|
|
// ... it might report an IO or encoding exception. |
|
|
handler.startDocument (); |
|
|
pushURL (false, "[document]", |
|
|
// default baseURI: null |
|
|
new String [] { publicId, systemId, null}, |
|
|
reader, stream, encoding, false); |
|
|
|
|
|
parseDocument (); |
|
|
} catch (EOFException e){ |
|
|
//empty input |
|
|
error("empty document, with no root element."); |
|
|
}finally { |
|
|
if (reader != null) |
|
|
try { reader.close (); |
|
|
} catch (IOException e) { /* ignore */ } |
|
|
if (stream != null) |
|
|
try { stream.close (); |
|
|
} catch (IOException e) { /* ignore */ } |
|
|
if (is != null) |
|
|
try { is.close (); |
|
|
} catch (IOException e) { /* ignore */ } |
|
|
if (reader != null) |
|
|
try { |
|
|
reader.close (); |
|
|
} catch (IOException e) { /* ignore */ |
|
|
} |
|
|
scratch = null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////// |
|
|
// Constants. |
|
|
//////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
// |
|
|
// Constants for element content type. |
|
|
// |
|
|
|
|
|
/** |
|
|
* Constant: an element has not been declared. |
|
|
* @see #getElementContentType |
|
|
*/ |
|
|
public final static int CONTENT_UNDECLARED = 0; |
|
|
|
|
|
/** |
|
|
* Constant: the element has a content model of ANY. |
|
|
* @see #getElementContentType |
|
|
*/ |
|
|
public final static int CONTENT_ANY = 1; |
|
|
|
|
|
/** |
|
|
* Constant: the element has declared content of EMPTY. |
|
|
* @see #getElementContentType |
|
|
*/ |
|
|
public final static int CONTENT_EMPTY = 2; |
|
90 |
|
|
91 |
/** |
// avoid slow per-character readCh() |
92 |
* Constant: the element has mixed content. |
private final static boolean USE_CHEATS = true; |
|
* @see #getElementContentType |
|
|
*/ |
|
|
public final static int CONTENT_MIXED = 3; |
|
|
|
|
|
/** |
|
|
* Constant: the element has element content. |
|
|
* @see #getElementContentType |
|
|
*/ |
|
|
public final static int CONTENT_ELEMENTS = 4; |
|
|
|
|
|
|
|
|
// |
|
|
// Constants for the entity type. |
|
|
// |
|
|
|
|
|
/** |
|
|
* Constant: the entity has not been declared. |
|
|
* @see #getEntityType |
|
|
*/ |
|
|
public final static int ENTITY_UNDECLARED = 0; |
|
|
|
|
|
/** |
|
|
* Constant: the entity is internal. |
|
|
* @see #getEntityType |
|
|
*/ |
|
|
public final static int ENTITY_INTERNAL = 1; |
|
|
|
|
|
/** |
|
|
* Constant: the entity is external, non-parsable data. |
|
|
* @see #getEntityType |
|
|
*/ |
|
|
public final static int ENTITY_NDATA = 2; |
|
|
|
|
|
/** |
|
|
* Constant: the entity is external XML data. |
|
|
* @see #getEntityType |
|
|
*/ |
|
|
public final static int ENTITY_TEXT = 3; |
|
|
|
|
|
|
|
|
// |
|
|
// Attribute type constants are interned literal strings. |
|
|
// |
|
|
|
|
|
// |
|
|
// Constants for supported encodings. "external" is just a flag. |
|
|
// |
|
|
private final static int ENCODING_EXTERNAL = 0; |
|
|
private final static int ENCODING_UTF_8 = 1; |
|
|
private final static int ENCODING_ISO_8859_1 = 2; |
|
|
private final static int ENCODING_UCS_2_12 = 3; |
|
|
private final static int ENCODING_UCS_2_21 = 4; |
|
|
private final static int ENCODING_UCS_4_1234 = 5; |
|
|
private final static int ENCODING_UCS_4_4321 = 6; |
|
|
private final static int ENCODING_UCS_4_2143 = 7; |
|
|
private final static int ENCODING_UCS_4_3412 = 8; |
|
|
private final static int ENCODING_ASCII = 9; |
|
|
|
|
|
|
|
|
// |
|
|
// Constants for attribute default value. |
|
|
// |
|
|
|
|
|
/** |
|
|
* Constant: the attribute is not declared. |
|
|
* @see #getAttributeDefaultValueType |
|
|
*/ |
|
|
public final static int ATTRIBUTE_DEFAULT_UNDECLARED = 30; |
|
|
|
|
|
/** |
|
|
* Constant: the attribute has a literal default value specified. |
|
|
* @see #getAttributeDefaultValueType |
|
|
* @see #getAttributeDefaultValue |
|
|
*/ |
|
|
public final static int ATTRIBUTE_DEFAULT_SPECIFIED = 31; |
|
|
|
|
|
/** |
|
|
* Constant: the attribute was declared #IMPLIED. |
|
|
* @see #getAttributeDefaultValueType |
|
|
*/ |
|
|
public final static int ATTRIBUTE_DEFAULT_IMPLIED = 32; |
|
|
|
|
|
/** |
|
|
* Constant: the attribute was declared #REQUIRED. |
|
|
* @see #getAttributeDefaultValueType |
|
|
*/ |
|
|
public final static int ATTRIBUTE_DEFAULT_REQUIRED = 33; |
|
|
|
|
|
/** |
|
|
* Constant: the attribute was declared #FIXED. |
|
|
* @see #getAttributeDefaultValueType |
|
|
* @see #getAttributeDefaultValue |
|
|
*/ |
|
|
public final static int ATTRIBUTE_DEFAULT_FIXED = 34; |
|
|
|
|
|
|
|
|
// |
|
|
// Constants for input. |
|
|
// |
|
|
private final static int INPUT_NONE = 0; |
|
|
private final static int INPUT_INTERNAL = 1; |
|
|
private final static int INPUT_STREAM = 3; |
|
|
private final static int INPUT_READER = 5; |
|
|
|
|
|
|
|
|
// |
|
|
// Flags for reading literals. |
|
|
// |
|
|
// expand general entity refs (attribute values in dtd and content) |
|
|
private final static int LIT_ENTITY_REF = 2; |
|
|
// normalize this value (space chars) (attributes, public ids) |
|
|
private final static int LIT_NORMALIZE = 4; |
|
|
// literal is an attribute value |
|
|
private final static int LIT_ATTRIBUTE = 8; |
|
|
// don't expand parameter entities |
|
|
private final static int LIT_DISABLE_PE = 16; |
|
|
// don't expand [or parse] character refs |
|
|
private final static int LIT_DISABLE_CREF = 32; |
|
|
// don't parse general entity refs |
|
|
private final static int LIT_DISABLE_EREF = 64; |
|
|
// literal is a public ID value |
|
|
private final static int LIT_PUBID = 256; |
|
93 |
|
|
94 |
|
//////////////////////////////////////////////////////////////////////// |
95 |
|
// Constants. |
96 |
|
//////////////////////////////////////////////////////////////////////// |
97 |
|
|
98 |
|
// |
99 |
|
// Constants for element content type. |
100 |
|
// |
101 |
|
|
102 |
|
/** |
103 |
|
* Constant: an element has not been declared. |
104 |
|
* @see #getElementContentType |
105 |
|
*/ |
106 |
|
public final static int CONTENT_UNDECLARED = 0; |
107 |
|
|
108 |
|
/** |
109 |
|
* Constant: the element has a content model of ANY. |
110 |
|
* @see #getElementContentType |
111 |
|
*/ |
112 |
|
public final static int CONTENT_ANY = 1; |
113 |
|
|
114 |
|
/** |
115 |
|
* Constant: the element has declared content of EMPTY. |
116 |
|
* @see #getElementContentType |
117 |
|
*/ |
118 |
|
public final static int CONTENT_EMPTY = 2; |
119 |
|
|
120 |
|
/** |
121 |
|
* Constant: the element has mixed content. |
122 |
|
* @see #getElementContentType |
123 |
|
*/ |
124 |
|
public final static int CONTENT_MIXED = 3; |
125 |
|
|
126 |
|
/** |
127 |
|
* Constant: the element has element content. |
128 |
|
* @see #getElementContentType |
129 |
|
*/ |
130 |
|
public final static int CONTENT_ELEMENTS = 4; |
131 |
|
|
132 |
|
|
133 |
|
// |
134 |
|
// Constants for the entity type. |
135 |
|
// |
136 |
|
|
137 |
|
/** |
138 |
|
* Constant: the entity has not been declared. |
139 |
|
* @see #getEntityType |
140 |
|
*/ |
141 |
|
public final static int ENTITY_UNDECLARED = 0; |
142 |
|
|
143 |
|
/** |
144 |
|
* Constant: the entity is internal. |
145 |
|
* @see #getEntityType |
146 |
|
*/ |
147 |
|
public final static int ENTITY_INTERNAL = 1; |
148 |
|
|
149 |
|
/** |
150 |
|
* Constant: the entity is external, non-parsable data. |
151 |
|
* @see #getEntityType |
152 |
|
*/ |
153 |
|
public final static int ENTITY_NDATA = 2; |
154 |
|
|
155 |
|
/** |
156 |
|
* Constant: the entity is external XML data. |
157 |
|
* @see #getEntityType |
158 |
|
*/ |
159 |
|
public final static int ENTITY_TEXT = 3; |
160 |
|
|
161 |
|
// |
162 |
|
// Attribute type constants are interned literal strings. |
163 |
|
// |
164 |
|
|
165 |
|
// |
166 |
|
// Constants for supported encodings. "external" is just a flag. |
167 |
|
// |
168 |
|
private final static int ENCODING_EXTERNAL = 0; |
169 |
|
private final static int ENCODING_UTF_8 = 1; |
170 |
|
private final static int ENCODING_ISO_8859_1 = 2; |
171 |
|
private final static int ENCODING_UCS_2_12 = 3; |
172 |
|
private final static int ENCODING_UCS_2_21 = 4; |
173 |
|
private final static int ENCODING_UCS_4_1234 = 5; |
174 |
|
private final static int ENCODING_UCS_4_4321 = 6; |
175 |
|
private final static int ENCODING_UCS_4_2143 = 7; |
176 |
|
private final static int ENCODING_UCS_4_3412 = 8; |
177 |
|
private final static int ENCODING_ASCII = 9; |
178 |
|
|
179 |
|
// |
180 |
|
// Constants for attribute default value. |
181 |
|
// |
182 |
|
|
183 |
|
/** |
184 |
|
* Constant: the attribute is not declared. |
185 |
|
* @see #getAttributeDefaultValueType |
186 |
|
*/ |
187 |
|
public final static int ATTRIBUTE_DEFAULT_UNDECLARED = 30; |
188 |
|
|
189 |
|
/** |
190 |
|
* Constant: the attribute has a literal default value specified. |
191 |
|
* @see #getAttributeDefaultValueType |
192 |
|
* @see #getAttributeDefaultValue |
193 |
|
*/ |
194 |
|
public final static int ATTRIBUTE_DEFAULT_SPECIFIED = 31; |
195 |
|
|
196 |
|
/** |
197 |
|
* Constant: the attribute was declared #IMPLIED. |
198 |
|
* @see #getAttributeDefaultValueType |
199 |
|
*/ |
200 |
|
public final static int ATTRIBUTE_DEFAULT_IMPLIED = 32; |
201 |
|
|
202 |
|
/** |
203 |
|
* Constant: the attribute was declared #REQUIRED. |
204 |
|
* @see #getAttributeDefaultValueType |
205 |
|
*/ |
206 |
|
public final static int ATTRIBUTE_DEFAULT_REQUIRED = 33; |
207 |
|
|
208 |
|
/** |
209 |
|
* Constant: the attribute was declared #FIXED. |
210 |
|
* @see #getAttributeDefaultValueType |
211 |
|
* @see #getAttributeDefaultValue |
212 |
|
*/ |
213 |
|
public final static int ATTRIBUTE_DEFAULT_FIXED = 34; |
214 |
|
|
215 |
|
// |
216 |
|
// Constants for input. |
217 |
|
// |
218 |
|
private final static int INPUT_NONE = 0; |
219 |
|
private final static int INPUT_INTERNAL = 1; |
220 |
|
private final static int INPUT_STREAM = 3; |
221 |
|
private final static int INPUT_READER = 5; |
222 |
|
|
223 |
|
// |
224 |
|
// Flags for reading literals. |
225 |
|
// |
226 |
|
// expand general entity refs (attribute values in dtd and content) |
227 |
|
private final static int LIT_ENTITY_REF = 2; |
228 |
|
// normalize this value (space chars) (attributes, public ids) |
229 |
|
private final static int LIT_NORMALIZE = 4; |
230 |
|
// literal is an attribute value |
231 |
|
private final static int LIT_ATTRIBUTE = 8; |
232 |
|
// don't expand parameter entities |
233 |
|
private final static int LIT_DISABLE_PE = 16; |
234 |
|
// don't expand [or parse] character refs |
235 |
|
private final static int LIT_DISABLE_CREF = 32; |
236 |
|
// don't parse general entity refs |
237 |
|
private final static int LIT_DISABLE_EREF = 64; |
238 |
|
// literal is a public ID value |
239 |
|
private final static int LIT_PUBID = 256; |
240 |
|
|
241 |
|
// |
242 |
|
// Flags affecting PE handling in DTDs (if expandPE is true). |
243 |
|
// PEs expand with space padding, except inside literals. |
244 |
|
// |
245 |
|
private final static int CONTEXT_NORMAL = 0; |
246 |
|
private final static int CONTEXT_LITERAL = 1; |
247 |
|
|
248 |
|
// Emit warnings for relative URIs with no base URI. |
249 |
|
static boolean uriWarnings; |
250 |
|
static |
251 |
|
{ |
252 |
|
String key = "gnu.xml.aelfred2.XmlParser.uriWarnings"; |
253 |
|
GetPropertyAction a = new GetPropertyAction(key); |
254 |
|
uriWarnings = "true".equals(AccessController.doPrivileged(a)); |
255 |
|
} |
256 |
|
|
257 |
|
// |
258 |
|
// The current XML handler interface. |
259 |
|
// |
260 |
|
private SAXDriver handler; |
261 |
|
|
262 |
|
// |
263 |
|
// I/O information. |
264 |
|
// |
265 |
|
private Reader reader; // current reader |
266 |
|
private InputStream is; // current input stream |
267 |
|
private int line; // current line number |
268 |
|
private int column; // current column number |
269 |
|
private int sourceType; // type of input source |
270 |
|
private LinkedList inputStack; // stack of input soruces |
271 |
|
private URLConnection externalEntity; // current external entity |
272 |
|
private int encoding; // current character encoding |
273 |
|
private int currentByteCount; // bytes read from current source |
274 |
|
private InputSource scratch; // temporary |
275 |
|
|
276 |
|
// |
277 |
|
// Buffers for decoded but unparsed character input. |
278 |
|
// |
279 |
|
private char[] readBuffer; |
280 |
|
private int readBufferPos; |
281 |
|
private int readBufferLength; |
282 |
|
private int readBufferOverflow; // overflow from last data chunk. |
283 |
|
|
284 |
|
// |
285 |
|
// Buffer for undecoded raw byte input. |
286 |
|
// |
287 |
|
private final static int READ_BUFFER_MAX = 16384; |
288 |
|
private byte[] rawReadBuffer; |
289 |
|
|
290 |
|
|
291 |
|
// |
292 |
|
// Buffer for attribute values, char refs, DTD stuff. |
293 |
|
// |
294 |
|
private static int DATA_BUFFER_INITIAL = 4096; |
295 |
|
private char[] dataBuffer; |
296 |
|
private int dataBufferPos; |
297 |
|
|
298 |
|
// |
299 |
|
// Buffer for parsed names. |
300 |
|
// |
301 |
|
private static int NAME_BUFFER_INITIAL = 1024; |
302 |
|
private char[] nameBuffer; |
303 |
|
private int nameBufferPos; |
304 |
|
|
305 |
|
// |
306 |
|
// Save any standalone flag |
307 |
|
// |
308 |
|
private boolean docIsStandalone; |
309 |
|
|
310 |
|
// |
311 |
|
// Hashtables for DTD information on elements, entities, and notations. |
312 |
|
// Populated until we start ignoring decls (because of skipping a PE) |
313 |
|
// |
314 |
|
private HashMap elementInfo; |
315 |
|
private HashMap entityInfo; |
316 |
|
private HashMap notationInfo; |
317 |
|
private boolean skippedPE; |
318 |
|
|
319 |
|
// |
320 |
|
// Element type currently in force. |
321 |
|
// |
322 |
|
private String currentElement; |
323 |
|
private int currentElementContent; |
324 |
|
|
325 |
|
// |
326 |
|
// Stack of entity names, to detect recursion. |
327 |
|
// |
328 |
|
private LinkedList entityStack; |
329 |
|
|
330 |
|
// |
331 |
|
// PE expansion is enabled in most chunks of the DTD, not all. |
332 |
|
// When it's enabled, literals are treated differently. |
333 |
|
// |
334 |
|
private boolean inLiteral; |
335 |
|
private boolean expandPE; |
336 |
|
private boolean peIsError; |
337 |
|
|
338 |
|
// |
339 |
|
// can't report entity expansion inside two constructs: |
340 |
|
// - attribute expansions (internal entities only) |
341 |
|
// - markup declarations (parameter entities only) |
342 |
|
// |
343 |
|
private boolean doReport; |
344 |
|
|
345 |
|
// |
346 |
|
// Symbol table, for caching interned names. |
347 |
|
// |
348 |
|
// These show up wherever XML names or nmtokens are used: naming elements, |
349 |
|
// attributes, PIs, notations, entities, and enumerated attribute values. |
350 |
|
// |
351 |
|
// NOTE: This hashtable doesn't grow. The default size is intended to be |
352 |
|
// rather large for most documents. Example: one snapshot of the DocBook |
353 |
|
// XML 4.1 DTD used only about 350 such names. As a rule, only pathological |
354 |
|
// documents (ones that don't reuse names) should ever see much collision. |
355 |
|
// |
356 |
|
// Be sure that SYMBOL_TABLE_LENGTH always stays prime, for best hashing. |
357 |
|
// "2039" keeps the hash table size at about two memory pages on typical |
358 |
|
// 32 bit hardware. |
359 |
|
// |
360 |
|
private final static int SYMBOL_TABLE_LENGTH = 2039; |
361 |
|
|
362 |
|
private Object[][] symbolTable; |
363 |
|
|
364 |
|
// |
365 |
|
// Hash table of attributes found in current start tag. |
366 |
|
// |
367 |
|
private String[] tagAttributes; |
368 |
|
private int tagAttributePos; |
369 |
|
|
370 |
|
// |
371 |
|
// Utility flag: have we noticed a CR while reading the last |
372 |
|
// data chunk? If so, we will have to go back and normalise |
373 |
|
// CR or CR/LF line ends. |
374 |
|
// |
375 |
|
private boolean sawCR; |
376 |
|
|
377 |
|
// |
378 |
|
// Utility flag: are we in CDATA? If so, whitespace isn't ignorable. |
379 |
|
// |
380 |
|
private boolean inCDATA; |
381 |
|
|
382 |
|
// |
383 |
|
// Xml version. |
384 |
|
// |
385 |
|
private static final int XML_10 = 0; |
386 |
|
private static final int XML_11 = 1; |
387 |
|
private int xmlVersion = XML_10; |
388 |
|
|
389 |
|
////////////////////////////////////////////////////////////////////// |
390 |
|
// Constructors. |
391 |
|
//////////////////////////////////////////////////////////////////////// |
392 |
|
|
393 |
|
/** |
394 |
|
* Construct a new parser with no associated handler. |
395 |
|
* @see #setHandler |
396 |
|
* @see #parse |
397 |
|
*/ |
398 |
|
// package private |
399 |
|
XmlParser() |
400 |
|
{ |
401 |
|
} |
402 |
|
|
403 |
// |
/** |
404 |
// Flags affecting PE handling in DTDs (if expandPE is true). |
* Set the handler that will receive parsing events. |
405 |
// PEs expand with space padding, except inside literals. |
* @param handler The handler to receive callback events. |
406 |
// |
* @see #parse |
407 |
private final static int CONTEXT_NORMAL = 0; |
*/ |
408 |
private final static int CONTEXT_LITERAL = 1; |
// package private |
409 |
|
void setHandler(SAXDriver handler) |
410 |
|
{ |
411 |
|
this.handler = handler; |
412 |
|
} |
413 |
|
|
414 |
|
/** |
415 |
|
* Parse an XML document from the character stream, byte stream, or URI |
416 |
|
* that you provide (in that order of preference). Any URI that you |
417 |
|
* supply will become the base URI for resolving relative URI, and may |
418 |
|
* be used to acquire a reader or byte stream. |
419 |
|
* |
420 |
|
* <p> Only one thread at a time may use this parser; since it is |
421 |
|
* private to this package, post-parse cleanup is done by the caller, |
422 |
|
* which MUST NOT REUSE the parser (just null it). |
423 |
|
* |
424 |
|
* @param systemId Absolute URI of the document; should never be null, |
425 |
|
* but may be so iff a reader <em>or</em> a stream is provided. |
426 |
|
* @param publicId The public identifier of the document, or null. |
427 |
|
* @param reader A character stream; must be null if stream isn't. |
428 |
|
* @param stream A byte input stream; must be null if reader isn't. |
429 |
|
* @param encoding The suggested encoding, or null if unknown. |
430 |
|
* @exception java.lang.Exception Basically SAXException or IOException |
431 |
|
*/ |
432 |
|
// package private |
433 |
|
void doParse(String systemId, String publicId, Reader reader, |
434 |
|
InputStream stream, String encoding) |
435 |
|
throws Exception |
436 |
|
{ |
437 |
|
if (handler == null) |
438 |
|
{ |
439 |
|
throw new IllegalStateException("no callback handler"); |
440 |
|
} |
441 |
|
|
442 |
////////////////////////////////////////////////////////////////////// |
initializeVariables(); |
|
// Error reporting. |
|
|
////////////////////////////////////////////////////////////////////// |
|
443 |
|
|
444 |
|
// predeclare the built-in entities here (replacement texts) |
445 |
|
// we don't need to intern(), since we're guaranteed literals |
446 |
|
// are always (globally) interned. |
447 |
|
setInternalEntity("amp", "&"); |
448 |
|
setInternalEntity("lt", "<"); |
449 |
|
setInternalEntity("gt", ">"); |
450 |
|
setInternalEntity("apos", "'"); |
451 |
|
setInternalEntity("quot", """); |
452 |
|
|
453 |
|
try |
454 |
|
{ |
455 |
|
// pushURL first to ensure locator is correct in startDocument |
456 |
|
// ... it might report an IO or encoding exception. |
457 |
|
handler.startDocument(); |
458 |
|
pushURL(false, "[document]", |
459 |
|
// default baseURI: null |
460 |
|
new ExternalIdentifiers(publicId, systemId, null), |
461 |
|
reader, stream, encoding, false); |
462 |
|
|
463 |
|
parseDocument(); |
464 |
|
} |
465 |
|
catch (EOFException e) |
466 |
|
{ |
467 |
|
//empty input |
468 |
|
error("empty document, with no root element."); |
469 |
|
} |
470 |
|
finally |
471 |
|
{ |
472 |
|
if (reader != null) |
473 |
|
{ |
474 |
|
try |
475 |
|
{ |
476 |
|
reader.close(); |
477 |
|
} |
478 |
|
catch (IOException e) |
479 |
|
{ |
480 |
|
/* ignore */ |
481 |
|
} |
482 |
|
} |
483 |
|
if (stream != null) |
484 |
|
{ |
485 |
|
try |
486 |
|
{ |
487 |
|
stream.close(); |
488 |
|
} |
489 |
|
catch (IOException e) |
490 |
|
{ |
491 |
|
/* ignore */ |
492 |
|
} |
493 |
|
} |
494 |
|
if (is != null) |
495 |
|
{ |
496 |
|
try |
497 |
|
{ |
498 |
|
is.close(); |
499 |
|
} |
500 |
|
catch (IOException e) |
501 |
|
{ |
502 |
|
/* ignore */ |
503 |
|
} |
504 |
|
} |
505 |
|
scratch = null; |
506 |
|
} |
507 |
|
} |
508 |
|
|
509 |
/** |
////////////////////////////////////////////////////////////////////// |
510 |
* Report an error. |
// Error reporting. |
511 |
* @param message The error message. |
////////////////////////////////////////////////////////////////////// |
512 |
* @param textFound The text that caused the error (or null). |
|
513 |
* @see SAXDriver#error |
/** |
514 |
* @see #line |
* Report an error. |
515 |
*/ |
* @param message The error message. |
516 |
private void error (String message, String textFound, String textExpected) |
* @param textFound The text that caused the error (or null). |
517 |
|
* @see SAXDriver#error |
518 |
|
* @see #line |
519 |
|
*/ |
520 |
|
private void error(String message, String textFound, String textExpected) |
521 |
throws SAXException |
throws SAXException |
522 |
{ |
{ |
523 |
if (textFound != null) { |
if (textFound != null) |
524 |
message = message + " (found \"" + textFound + "\")"; |
{ |
525 |
} |
message = message + " (found \"" + textFound + "\")"; |
526 |
if (textExpected != null) { |
} |
527 |
message = message + " (expected \"" + textExpected + "\")"; |
if (textExpected != null) |
528 |
} |
{ |
529 |
handler.fatal (message); |
message = message + " (expected \"" + textExpected + "\")"; |
530 |
|
} |
531 |
// "can't happen" |
handler.fatal(message); |
532 |
throw new SAXException (message); |
|
533 |
} |
// "can't happen" |
534 |
|
throw new SAXException(message); |
535 |
|
} |
536 |
|
|
537 |
/** |
/** |
538 |
* Report a serious error. |
* Report a serious error. |
539 |
* @param message The error message. |
* @param message The error message. |
540 |
* @param textFound The text that caused the error (or null). |
* @param textFound The text that caused the error (or null). |
541 |
*/ |
*/ |
542 |
private void error (String message, char textFound, String textExpected) |
private void error(String message, char textFound, String textExpected) |
543 |
throws SAXException |
throws SAXException |
544 |
{ |
{ |
545 |
error (message, new Character (textFound).toString (), textExpected); |
error(message, new Character(textFound).toString(), textExpected); |
546 |
} |
} |
547 |
|
|
548 |
/** Report typical case fatal errors. */ |
/** |
549 |
private void error (String message) |
* Report typical case fatal errors. |
550 |
|
*/ |
551 |
|
private void error(String message) |
552 |
throws SAXException |
throws SAXException |
553 |
{ |
{ |
554 |
handler.fatal (message); |
handler.fatal(message); |
555 |
} |
} |
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
// Major syntactic productions. |
|
|
////////////////////////////////////////////////////////////////////// |
|
556 |
|
|
557 |
|
////////////////////////////////////////////////////////////////////// |
558 |
|
// Major syntactic productions. |
559 |
|
////////////////////////////////////////////////////////////////////// |
560 |
|
|
561 |
/** |
/** |
562 |
* Parse an XML document. |
* Parse an XML document. |
563 |
* <pre> |
* <pre> |
564 |
* [1] document ::= prolog element Misc* |
* [1] document ::= prolog element Misc* |
565 |
* </pre> |
* </pre> |
566 |
* <p>This is the top-level parsing function for a single XML |
* <p>This is the top-level parsing function for a single XML |
567 |
* document. As a minimum, a well-formed document must have |
* document. As a minimum, a well-formed document must have |
568 |
* a document element, and a valid document must have a prolog |
* a document element, and a valid document must have a prolog |
569 |
* (one with doctype) as well. |
* (one with doctype) as well. |
570 |
*/ |
*/ |
571 |
private void parseDocument () |
private void parseDocument() |
572 |
throws Exception |
throws Exception |
573 |
{ |
{ |
574 |
try { // added by MHK |
try |
575 |
boolean sawDTD = parseProlog (); |
{ // added by MHK |
576 |
require ('<'); |
boolean sawDTD = parseProlog(); |
577 |
parseElement (!sawDTD); |
require('<'); |
578 |
} catch (EOFException ee) { // added by MHK |
parseElement(!sawDTD); |
579 |
error("premature end of file", "[EOF]", null); |
} |
580 |
} |
catch (EOFException ee) |
581 |
|
{ // added by MHK |
582 |
try { |
error("premature end of file", "[EOF]", null); |
583 |
parseMisc (); //skip all white, PIs, and comments |
} |
584 |
char c = readCh (); //if this doesn't throw an exception... |
|
585 |
error ("unexpected characters after document end", c, null); |
try |
586 |
} catch (EOFException e) { |
{ |
587 |
return; |
parseMisc(); //skip all white, PIs, and comments |
588 |
} |
char c = readCh(); //if this doesn't throw an exception... |
589 |
} |
error("unexpected characters after document end", c, null); |
590 |
|
} |
591 |
static final char startDelimComment [] = { '<', '!', '-', '-' }; |
catch (EOFException e) |
592 |
static final char endDelimComment [] = { '-', '-' }; |
{ |
593 |
|
return; |
594 |
|
} |
595 |
|
} |
596 |
|
|
597 |
|
static final char[] startDelimComment = { '<', '!', '-', '-' }; |
598 |
|
static final char[] endDelimComment = { '-', '-' }; |
599 |
|
|
600 |
/** |
/** |
601 |
* Skip a comment. |
* Skip a comment. |
602 |
* <pre> |
* <pre> |
603 |
* [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* "-->" |
* [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* "-->" |
604 |
* </pre> |
* </pre> |
605 |
* <p> (The <code><!--</code> has already been read.) |
* <p> (The <code><!--</code> has already been read.) |
606 |
*/ |
*/ |
607 |
private void parseComment () |
private void parseComment() |
608 |
throws Exception |
throws Exception |
609 |
{ |
{ |
610 |
char c; |
char c; |
611 |
boolean saved = expandPE; |
boolean saved = expandPE; |
612 |
|
|
613 |
expandPE = false; |
expandPE = false; |
614 |
parseUntil (endDelimComment); |
parseUntil(endDelimComment); |
615 |
require ('>'); |
require('>'); |
616 |
expandPE = saved; |
expandPE = saved; |
617 |
handler.comment (dataBuffer, 0, dataBufferPos); |
handler.comment(dataBuffer, 0, dataBufferPos); |
618 |
dataBufferPos = 0; |
dataBufferPos = 0; |
619 |
} |
} |
620 |
|
|
621 |
static final char startDelimPI [] = { '<', '?' }; |
static final char[] startDelimPI = { '<', '?' }; |
622 |
static final char endDelimPI [] = { '?', '>' }; |
static final char[] endDelimPI = { '?', '>' }; |
623 |
|
|
624 |
/** |
/** |
625 |
* Parse a processing instruction and do a call-back. |
* Parse a processing instruction and do a call-back. |
626 |
* <pre> |
* <pre> |
627 |
* [16] PI ::= '<?' PITarget |
* [16] PI ::= '<?' PITarget |
628 |
* (S (Char* - (Char* '?>' Char*)))? |
* (S (Char* - (Char* '?>' Char*)))? |
629 |
* '?>' |
* '?>' |
630 |
* [17] PITarget ::= Name - ( ('X'|'x') ('M'|m') ('L'|l') ) |
* [17] PITarget ::= Name - ( ('X'|'x') ('M'|m') ('L'|l') ) |
631 |
* </pre> |
* </pre> |
632 |
* <p> (The <code><?</code> has already been read.) |
* <p> (The <code><?</code> has already been read.) |
633 |
*/ |
*/ |
634 |
private void parsePI () |
private void parsePI() |
635 |
throws SAXException, IOException |
throws SAXException, IOException |
636 |
{ |
{ |
637 |
String name; |
String name; |
638 |
boolean saved = expandPE; |
boolean saved = expandPE; |
639 |
|
|
640 |
expandPE = false; |
expandPE = false; |
641 |
name = readNmtoken (true); |
name = readNmtoken(true); |
642 |
//NE08 |
//NE08 |
643 |
if (name.indexOf(':') >= 0) |
if (name.indexOf(':') >= 0) |
644 |
error ("Illegal character(':') in processing instruction name ", name, null); |
{ |
645 |
if ("xml".equalsIgnoreCase (name)) |
error("Illegal character(':') in processing instruction name ", |
646 |
error ("Illegal processing instruction target", name, null); |
name, null); |
647 |
if (!tryRead (endDelimPI)) { |
} |
648 |
requireWhitespace (); |
if ("xml".equalsIgnoreCase(name)) |
649 |
parseUntil (endDelimPI); |
{ |
650 |
} |
error("Illegal processing instruction target", name, null); |
651 |
expandPE = saved; |
} |
652 |
handler.processingInstruction (name, dataBufferToString ()); |
if (!tryRead(endDelimPI)) |
653 |
} |
{ |
654 |
|
requireWhitespace(); |
655 |
|
parseUntil(endDelimPI); |
656 |
static final char endDelimCDATA [] = { ']', ']', '>' }; |
} |
657 |
|
expandPE = saved; |
658 |
|
handler.processingInstruction(name, dataBufferToString()); |
659 |
|
} |
660 |
|
|
661 |
|
static final char[] endDelimCDATA = { ']', ']', '>' }; |
662 |
|
|
663 |
private boolean isDirtyCurrentElement; |
private boolean isDirtyCurrentElement; |
664 |
|
|
665 |
/** |
/** |
666 |
* Parse a CDATA section. |
* Parse a CDATA section. |
667 |
* <pre> |
* <pre> |
668 |
* [18] CDSect ::= CDStart CData CDEnd |
* [18] CDSect ::= CDStart CData CDEnd |
669 |
* [19] CDStart ::= '<![CDATA[' |
* [19] CDStart ::= '<![CDATA[' |
670 |
* [20] CData ::= (Char* - (Char* ']]>' Char*)) |
* [20] CData ::= (Char* - (Char* ']]>' Char*)) |
671 |
* [21] CDEnd ::= ']]>' |
* [21] CDEnd ::= ']]>' |
672 |
* </pre> |
* </pre> |
673 |
* <p> (The '<![CDATA[' has already been read.) |
* <p> (The '<![CDATA[' has already been read.) |
674 |
*/ |
*/ |
675 |
private void parseCDSect () |
private void parseCDSect() |
676 |
throws Exception |
throws Exception |
677 |
{ |
{ |
678 |
parseUntil (endDelimCDATA); |
parseUntil(endDelimCDATA); |
679 |
dataBufferFlush (); |
dataBufferFlush(); |
680 |
} |
} |
|
|
|
681 |
|
|
682 |
/** |
/** |
683 |
* Parse the prolog of an XML document. |
* Parse the prolog of an XML document. |
684 |
* <pre> |
* <pre> |
685 |
* [22] prolog ::= XMLDecl? Misc* (Doctypedecl Misc*)? |
* [22] prolog ::= XMLDecl? Misc* (Doctypedecl Misc*)? |
686 |
* </pre> |
* </pre> |
687 |
* <p>We do not look for the XML declaration here, because it was |
* <p>We do not look for the XML declaration here, because it was |
688 |
* handled by pushURL (). |
* handled by pushURL (). |
689 |
* @see pushURL |
* @see pushURL |
690 |
* @return true if a DTD was read. |
* @return true if a DTD was read. |
691 |
*/ |
*/ |
692 |
private boolean parseProlog () |
private boolean parseProlog() |
693 |
throws Exception |
throws Exception |
694 |
{ |
{ |
695 |
parseMisc (); |
parseMisc(); |
696 |
|
|
697 |
if (tryRead ("<!DOCTYPE")) { |
if (tryRead("<!DOCTYPE")) |
698 |
parseDoctypedecl (); |
{ |
699 |
parseMisc (); |
parseDoctypedecl(); |
700 |
return true; |
parseMisc(); |
701 |
} |
return true; |
702 |
return false; |
} |
703 |
} |
return false; |
704 |
|
} |
705 |
|
|
706 |
private void checkLegalVersion (String version) |
private void checkLegalVersion(String version) |
707 |
throws SAXException |
throws SAXException |
708 |
{ |
{ |
709 |
int len = version.length (); |
int len = version.length(); |
710 |
for (int i = 0; i < len; i++) { |
for (int i = 0; i < len; i++) |
711 |
char c = version.charAt (i); |
{ |
712 |
if ('0' <= c && c <= '9') |
char c = version.charAt(i); |
713 |
continue; |
if ('0' <= c && c <= '9') |
714 |
if (c == '_' || c == '.' || c == ':' || c == '-') |
{ |
715 |
continue; |
continue; |
716 |
if ('a' <= c && c <= 'z') |
} |
717 |
continue; |
if (c == '_' || c == '.' || c == ':' || c == '-') |
718 |
if ('A' <= c && c <= 'Z') |
{ |
719 |
continue; |
continue; |
720 |
error ("illegal character in version", version, "1.0"); |
} |
721 |
} |
if ('a' <= c && c <= 'z') |
722 |
} |
{ |
723 |
|
continue; |
724 |
|
} |
725 |
|
if ('A' <= c && c <= 'Z') |
726 |
|
{ |
727 |
|
continue; |
728 |
|
} |
729 |
|
error ("illegal character in version", version, "1.0"); |
730 |
|
} |
731 |
|
} |
732 |
|
|
733 |
/** |
/** |
734 |
* Parse the XML declaration. |
* Parse the XML declaration. |
735 |
* <pre> |
* <pre> |
736 |
* [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' |
* [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' |
737 |
* [24] VersionInfo ::= S 'version' Eq |
* [24] VersionInfo ::= S 'version' Eq |
738 |
* ("'" VersionNum "'" | '"' VersionNum '"' ) |
* ("'" VersionNum "'" | '"' VersionNum '"' ) |
739 |
* [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')* |
* [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')* |
740 |
* [32] SDDecl ::= S 'standalone' Eq |
* [32] SDDecl ::= S 'standalone' Eq |
741 |
* ( "'"" ('yes' | 'no') "'"" | '"' ("yes" | "no") '"' ) |
* ( "'"" ('yes' | 'no') "'"" | '"' ("yes" | "no") '"' ) |
742 |
* [80] EncodingDecl ::= S 'encoding' Eq |
* [80] EncodingDecl ::= S 'encoding' Eq |
743 |
* ( "'" EncName "'" | "'" EncName "'" ) |
* ( "'" EncName "'" | "'" EncName "'" ) |
744 |
* [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* |
* [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* |
745 |
* </pre> |
* </pre> |
746 |
* <p> (The <code><?xml</code> and whitespace have already been read.) |
* <p> (The <code><?xml</code> and whitespace have already been read.) |
747 |
* @return the encoding in the declaration, uppercased; or null |
* @return the encoding in the declaration, uppercased; or null |
748 |
* @see #parseTextDecl |
* @see #parseTextDecl |
749 |
* @see #setupDecoding |
* @see #setupDecoding |
750 |
*/ |
*/ |
751 |
private String parseXMLDecl (boolean ignoreEncoding) |
private String parseXMLDecl(boolean ignoreEncoding) |
752 |
throws SAXException, IOException |
throws SAXException, IOException |
753 |
{ |
{ |
754 |
String version; |
String version; |
755 |
String encodingName = null; |
String encodingName = null; |
756 |
String standalone = null; |
String standalone = null; |
757 |
int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; |
int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; |
758 |
String inputEncoding = null; |
String inputEncoding = null; |
759 |
|
|
760 |
switch (this.encoding) |
switch (this.encoding) |
761 |
|
{ |
762 |
|
case ENCODING_EXTERNAL: |
763 |
|
case ENCODING_UTF_8: |
764 |
|
inputEncoding = "UTF-8"; |
765 |
|
break; |
766 |
|
case ENCODING_ISO_8859_1: |
767 |
|
inputEncoding = "ISO-8859-1"; |
768 |
|
break; |
769 |
|
case ENCODING_UCS_2_12: |
770 |
|
inputEncoding = "UTF-16BE"; |
771 |
|
break; |
772 |
|
case ENCODING_UCS_2_21: |
773 |
|
inputEncoding = "UTF-16LE"; |
774 |
|
break; |
775 |
|
} |
776 |
|
|
777 |
|
// Read the version. |
778 |
|
require("version"); |
779 |
|
parseEq(); |
780 |
|
checkLegalVersion(version = readLiteral(flags)); |
781 |
|
if (!version.equals("1.0")) |
782 |
|
{ |
783 |
|
if (version.equals("1.1")) |
784 |
{ |
{ |
785 |
case ENCODING_EXTERNAL: |
handler.warn("expected XML version 1.0, not: " + version); |
786 |
case ENCODING_UTF_8: |
xmlVersion = XML_11; |
|
inputEncoding = "UTF-8"; |
|
|
break; |
|
|
case ENCODING_ISO_8859_1: |
|
|
inputEncoding = "ISO-8859-1"; |
|
|
break; |
|
|
case ENCODING_UCS_2_12: |
|
|
inputEncoding = "UTF-16BE"; |
|
|
break; |
|
|
case ENCODING_UCS_2_21: |
|
|
inputEncoding = "UTF-16LE"; |
|
|
break; |
|
787 |
} |
} |
788 |
|
else |
|
// Read the version. |
|
|
require ("version"); |
|
|
parseEq (); |
|
|
checkLegalVersion (version = readLiteral (flags)); |
|
|
if (!version.equals ("1.0")){ |
|
|
if(version.equals ("1.1")){ |
|
|
handler.warn ("expected XML version 1.0, not: " + version); |
|
|
xmlVersion = XML_11; |
|
|
}else { |
|
|
error("illegal XML version", version, "1.0 or 1.1"); |
|
|
} |
|
|
} |
|
|
else |
|
|
xmlVersion = XML_10; |
|
|
// Try reading an encoding declaration. |
|
|
boolean white = tryWhitespace (); |
|
|
|
|
|
if (tryRead ("encoding")) { |
|
|
if (!white) |
|
|
error ("whitespace required before 'encoding='"); |
|
|
parseEq (); |
|
|
encodingName = readLiteral (flags); |
|
|
if (!ignoreEncoding) |
|
|
setupDecoding (encodingName); |
|
|
} |
|
|
|
|
|
// Try reading a standalone declaration |
|
|
if (encodingName != null) |
|
|
white = tryWhitespace (); |
|
|
if (tryRead ("standalone")) { |
|
|
if (!white) |
|
|
error ("whitespace required before 'standalone='"); |
|
|
parseEq (); |
|
|
standalone = readLiteral (flags); |
|
|
if ("yes".equals (standalone)) |
|
|
docIsStandalone = true; |
|
|
else if (!"no".equals (standalone)) |
|
|
error ("standalone flag must be 'yes' or 'no'"); |
|
|
} |
|
|
|
|
|
skipWhitespace (); |
|
|
require ("?>"); |
|
|
|
|
|
if (inputEncoding == null) |
|
789 |
{ |
{ |
790 |
inputEncoding = encodingName; |
error("illegal XML version", version, "1.0 or 1.1"); |
791 |
} |
} |
792 |
handler.xmlDecl(version, encodingName, "yes".equals(standalone), |
} |
793 |
inputEncoding); |
else |
794 |
|
{ |
795 |
|
xmlVersion = XML_10; |
796 |
|
} |
797 |
|
// Try reading an encoding declaration. |
798 |
|
boolean white = tryWhitespace(); |
799 |
|
|
800 |
|
if (tryRead("encoding")) |
801 |
|
{ |
802 |
|
if (!white) |
803 |
|
{ |
804 |
|
error("whitespace required before 'encoding='"); |
805 |
|
} |
806 |
|
parseEq(); |
807 |
|
encodingName = readLiteral(flags); |
808 |
|
if (!ignoreEncoding) |
809 |
|
{ |
810 |
|
setupDecoding(encodingName); |
811 |
|
} |
812 |
|
} |
813 |
|
|
814 |
|
// Try reading a standalone declaration |
815 |
|
if (encodingName != null) |
816 |
|
{ |
817 |
|
white = tryWhitespace(); |
818 |
|
} |
819 |
|
if (tryRead("standalone")) |
820 |
|
{ |
821 |
|
if (!white) |
822 |
|
{ |
823 |
|
error("whitespace required before 'standalone='"); |
824 |
|
} |
825 |
|
parseEq(); |
826 |
|
standalone = readLiteral(flags); |
827 |
|
if ("yes".equals(standalone)) |
828 |
|
{ |
829 |
|
docIsStandalone = true; |
830 |
|
} |
831 |
|
else if (!"no".equals(standalone)) |
832 |
|
{ |
833 |
|
error("standalone flag must be 'yes' or 'no'"); |
834 |
|
} |
835 |
|
} |
836 |
|
|
837 |
return encodingName; |
skipWhitespace(); |
838 |
} |
require("?>"); |
839 |
|
|
840 |
|
if (inputEncoding == null) |
841 |
|
{ |
842 |
|
inputEncoding = encodingName; |
843 |
|
} |
844 |
|
handler.xmlDecl(version, encodingName, docIsStandalone, |
845 |
|
inputEncoding); |
846 |
|
|
847 |
|
return encodingName; |
848 |
|
} |
849 |
|
|
850 |
/** |
/** |
851 |
* Parse a text declaration. |
* Parse a text declaration. |
852 |
* <pre> |
* <pre> |
853 |
* [79] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>' |
* [79] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>' |
854 |
* [80] EncodingDecl ::= S 'encoding' Eq |
* [80] EncodingDecl ::= S 'encoding' Eq |
855 |
* ( '"' EncName '"' | "'" EncName "'" ) |
* ( '"' EncName '"' | "'" EncName "'" ) |
856 |
* [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* |
* [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* |
857 |
* </pre> |
* </pre> |
858 |
* <p> (The <code><?xml</code>' and whitespace have already been read.) |
* <p> (The <code><?xml</code>' and whitespace have already been read.) |
859 |
* @return the encoding in the declaration, uppercased; or null |
* @return the encoding in the declaration, uppercased; or null |
860 |
* @see #parseXMLDecl |
* @see #parseXMLDecl |
861 |
* @see #setupDecoding |
* @see #setupDecoding |
862 |
*/ |
*/ |
863 |
private String parseTextDecl (boolean ignoreEncoding) |
private String parseTextDecl(boolean ignoreEncoding) |
864 |
throws SAXException, IOException |
throws SAXException, IOException |
865 |
{ |
{ |
866 |
String encodingName = null; |
String encodingName = null; |
867 |
int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; |
int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; |
|
|
|
|
// Read an optional version. |
|
|
if (tryRead ("version")) { |
|
|
String version; |
|
|
parseEq (); |
|
|
checkLegalVersion (version = readLiteral (flags)); |
|
|
|
|
|
if (version.equals ("1.1")){ |
|
|
if (xmlVersion == XML_10){ |
|
|
error ("external subset has later version number.", "1.0", version); |
|
|
} |
|
|
handler.warn ("expected XML version 1.0, not: " + version); |
|
|
xmlVersion = XML_11; |
|
|
}else if(!version.equals ("1.0")) { |
|
|
error("illegal XML version", version, "1.0 or 1.1"); |
|
|
} |
|
|
requireWhitespace (); |
|
|
} |
|
|
|
|
|
|
|
|
// Read the encoding. |
|
|
require ("encoding"); |
|
|
parseEq (); |
|
|
encodingName = readLiteral (flags); |
|
|
if (!ignoreEncoding) |
|
|
setupDecoding (encodingName); |
|
|
|
|
|
skipWhitespace (); |
|
|
require ("?>"); |
|
|
|
|
|
return encodingName; |
|
|
} |
|
868 |
|
|
869 |
|
// Read an optional version. |
870 |
|
if (tryRead ("version")) |
871 |
|
{ |
872 |
|
String version; |
873 |
|
parseEq(); |
874 |
|
checkLegalVersion(version = readLiteral(flags)); |
875 |
|
|
876 |
|
if (version.equals("1.1")) |
877 |
|
{ |
878 |
|
if (xmlVersion == XML_10) |
879 |
|
{ |
880 |
|
error("external subset has later version number.", "1.0", |
881 |
|
version); |
882 |
|
} |
883 |
|
handler.warn("expected XML version 1.0, not: " + version); |
884 |
|
xmlVersion = XML_11; |
885 |
|
} |
886 |
|
else if (!version.equals("1.0")) |
887 |
|
{ |
888 |
|
error("illegal XML version", version, "1.0 or 1.1"); |
889 |
|
} |
890 |
|
requireWhitespace(); |
891 |
|
} |
892 |
|
|
893 |
|
// Read the encoding. |
894 |
|
require("encoding"); |
895 |
|
parseEq(); |
896 |
|
encodingName = readLiteral(flags); |
897 |
|
if (!ignoreEncoding) |
898 |
|
{ |
899 |
|
setupDecoding(encodingName); |
900 |
|
} |
901 |
|
skipWhitespace(); |
902 |
|
require("?>"); |
903 |
|
|
904 |
|
return encodingName; |
905 |
|
} |
906 |
|
|
907 |
/** |
/** |
908 |
* Sets up internal state so that we can decode an entity using the |
* Sets up internal state so that we can decode an entity using the |
909 |
* specified encoding. This is used when we start to read an entity |
* specified encoding. This is used when we start to read an entity |
910 |
* and we have been given knowledge of its encoding before we start to |
* and we have been given knowledge of its encoding before we start to |
911 |
* read any data (e.g. from a SAX input source or from a MIME type). |
* read any data (e.g. from a SAX input source or from a MIME type). |
912 |
* |
* |
913 |
* <p> It is also used after autodetection, at which point only very |
* <p> It is also used after autodetection, at which point only very |
914 |
* limited adjustments to the encoding may be used (switching between |
* limited adjustments to the encoding may be used (switching between |
915 |
* related builtin decoders). |
* related builtin decoders). |
916 |
* |
* |
917 |
* @param encodingName The name of the encoding specified by the user. |
* @param encodingName The name of the encoding specified by the user. |
918 |
* @exception IOException if the encoding isn't supported either |
* @exception IOException if the encoding isn't supported either |
919 |
* internally to this parser, or by the hosting JVM. |
* internally to this parser, or by the hosting JVM. |
920 |
* @see #parseXMLDecl |
* @see #parseXMLDecl |
921 |
* @see #parseTextDecl |
* @see #parseTextDecl |
922 |
*/ |
*/ |
923 |
private void setupDecoding (String encodingName) |
private void setupDecoding(String encodingName) |
924 |
throws SAXException, IOException |
throws SAXException, IOException |
925 |
{ |
{ |
926 |
encodingName = encodingName.toUpperCase (); |
encodingName = encodingName.toUpperCase(); |
927 |
|
|
928 |
// ENCODING_EXTERNAL indicates an encoding that wasn't |
// ENCODING_EXTERNAL indicates an encoding that wasn't |
929 |
// autodetected ... we can use builtin decoders, or |
// autodetected ... we can use builtin decoders, or |
930 |
// ones from the JVM (InputStreamReader). |
// ones from the JVM (InputStreamReader). |
931 |
|
|
932 |
// Otherwise we can only tweak what was autodetected, and |
// Otherwise we can only tweak what was autodetected, and |
933 |
// only for single byte (ASCII derived) builtin encodings. |
// only for single byte (ASCII derived) builtin encodings. |
934 |
|
|
935 |
// ASCII-derived encodings |
// ASCII-derived encodings |
936 |
if (encoding == ENCODING_UTF_8 || encoding == ENCODING_EXTERNAL) { |
if (encoding == ENCODING_UTF_8 || encoding == ENCODING_EXTERNAL) |
937 |
if (encodingName.equals ("ISO-8859-1") |
{ |
938 |
|| encodingName.equals ("8859_1") |
if (encodingName.equals("ISO-8859-1") |
939 |
|| encodingName.equals ("ISO8859_1") |
|| encodingName.equals("8859_1") |
940 |
) { |
|| encodingName.equals("ISO8859_1")) |
941 |
encoding = ENCODING_ISO_8859_1; |
{ |
942 |
return; |
encoding = ENCODING_ISO_8859_1; |
943 |
} else if (encodingName.equals ("US-ASCII") |
return; |
944 |
|| encodingName.equals ("ASCII")) { |
} |
945 |
encoding = ENCODING_ASCII; |
else if (encodingName.equals("US-ASCII") |
946 |
return; |
|| encodingName.equals("ASCII")) |
947 |
} else if (encodingName.equals ("UTF-8") |
{ |
948 |
|| encodingName.equals ("UTF8")) { |
encoding = ENCODING_ASCII; |
949 |
encoding = ENCODING_UTF_8; |
return; |
950 |
return; |
} |
951 |
} else if (encoding != ENCODING_EXTERNAL) { |
else if (encodingName.equals("UTF-8") |
952 |
// used to start with a new reader ... |
|| encodingName.equals("UTF8")) |
953 |
throw new UnsupportedEncodingException (encodingName); |
{ |
954 |
} |
encoding = ENCODING_UTF_8; |
955 |
// else fallthrough ... |
return; |
956 |
// it's ASCII-ish and something other than a builtin |
} |
957 |
} |
else if (encoding != ENCODING_EXTERNAL) |
958 |
|
{ |
959 |
// Unicode and such |
// used to start with a new reader ... |
960 |
if (encoding == ENCODING_UCS_2_12 || encoding == ENCODING_UCS_2_21) { |
throw new UnsupportedEncodingException(encodingName); |
961 |
if (!(encodingName.equals ("ISO-10646-UCS-2") |
} |
962 |
|| encodingName.equals ("UTF-16") |
// else fallthrough ... |
963 |
|| encodingName.equals ("UTF-16BE") |
// it's ASCII-ish and something other than a builtin |
964 |
|| encodingName.equals ("UTF-16LE"))) |
} |
965 |
error ("unsupported Unicode encoding", |
|
966 |
encodingName, |
// Unicode and such |
967 |
"UTF-16"); |
if (encoding == ENCODING_UCS_2_12 || encoding == ENCODING_UCS_2_21) |
968 |
return; |
{ |
969 |
} |
if (!(encodingName.equals("ISO-10646-UCS-2") |
970 |
|
|| encodingName.equals("UTF-16") |
971 |
// four byte encodings |
|| encodingName.equals("UTF-16BE") |
972 |
if (encoding == ENCODING_UCS_4_1234 |
|| encodingName.equals("UTF-16LE"))) |
973 |
|| encoding == ENCODING_UCS_4_4321 |
{ |
974 |
|| encoding == ENCODING_UCS_4_2143 |
error("unsupported Unicode encoding", encodingName, "UTF-16"); |
975 |
|| encoding == ENCODING_UCS_4_3412) { |
} |
976 |
// Strictly: "UCS-4" == "UTF-32BE"; also, "UTF-32LE" exists |
return; |
977 |
if (!encodingName.equals ("ISO-10646-UCS-4")) |
} |
978 |
error ("unsupported 32-bit encoding", |
|
979 |
encodingName, |
// four byte encodings |
980 |
"ISO-10646-UCS-4"); |
if (encoding == ENCODING_UCS_4_1234 |
981 |
return; |
|| encoding == ENCODING_UCS_4_4321 |
982 |
} |
|| encoding == ENCODING_UCS_4_2143 |
983 |
|
|| encoding == ENCODING_UCS_4_3412) |
984 |
// assert encoding == ENCODING_EXTERNAL |
{ |
985 |
// if (encoding != ENCODING_EXTERNAL) |
// Strictly: "UCS-4" == "UTF-32BE"; also, "UTF-32LE" exists |
986 |
// throw new RuntimeException ("encoding = " + encoding); |
if (!encodingName.equals("ISO-10646-UCS-4")) |
987 |
|
{ |
988 |
if (encodingName.equals ("UTF-16BE")) { |
error("unsupported 32-bit encoding", encodingName, |
989 |
encoding = ENCODING_UCS_2_12; |
"ISO-10646-UCS-4"); |
990 |
return; |
} |
991 |
} |
return; |
992 |
if (encodingName.equals ("UTF-16LE")) { |
} |
993 |
encoding = ENCODING_UCS_2_21; |
|
994 |
return; |
// assert encoding == ENCODING_EXTERNAL |
995 |
} |
// if (encoding != ENCODING_EXTERNAL) |
996 |
|
// throw new RuntimeException ("encoding = " + encoding); |
997 |
// We couldn't use the builtin decoders at all. But we can try to |
|
998 |
// create a reader, since we haven't messed up buffering. Tweak |
if (encodingName.equals("UTF-16BE")) |
999 |
// the encoding name if necessary. |
{ |
1000 |
|
encoding = ENCODING_UCS_2_12; |
1001 |
if (encodingName.equals ("UTF-16") |
return; |
1002 |
|| encodingName.equals ("ISO-10646-UCS-2")) |
} |
1003 |
encodingName = "Unicode"; |
if (encodingName.equals("UTF-16LE")) |
1004 |
// Ignoring all the EBCDIC aliases here |
{ |
1005 |
|
encoding = ENCODING_UCS_2_21; |
1006 |
reader = new InputStreamReader (is, encodingName); |
return; |
1007 |
sourceType = INPUT_READER; |
} |
1008 |
} |
|
1009 |
|
// We couldn't use the builtin decoders at all. But we can try to |
1010 |
|
// create a reader, since we haven't messed up buffering. Tweak |
1011 |
/** |
// the encoding name if necessary. |
1012 |
* Parse miscellaneous markup outside the document element and DOCTYPE |
|
1013 |
* declaration. |
if (encodingName.equals("UTF-16") |
1014 |
* <pre> |
|| encodingName.equals("ISO-10646-UCS-2")) |
1015 |
* [27] Misc ::= Comment | PI | S |
{ |
1016 |
* </pre> |
encodingName = "Unicode"; |
1017 |
*/ |
} |
1018 |
private void parseMisc () |
// Ignoring all the EBCDIC aliases here |
1019 |
|
|
1020 |
|
reader = new InputStreamReader(is, encodingName); |
1021 |
|
sourceType = INPUT_READER; |
1022 |
|
} |
1023 |
|
|
1024 |
|
/** |
1025 |
|
* Parse miscellaneous markup outside the document element and DOCTYPE |
1026 |
|
* declaration. |
1027 |
|
* <pre> |
1028 |
|
* [27] Misc ::= Comment | PI | S |
1029 |
|
* </pre> |
1030 |
|
*/ |
1031 |
|
private void parseMisc() |
1032 |
throws Exception |
throws Exception |
1033 |
{ |
{ |
1034 |
while (true) { |
while (true) |
1035 |
skipWhitespace (); |
{ |
1036 |
if (tryRead (startDelimPI)) { |
skipWhitespace(); |
1037 |
parsePI (); |
if (tryRead(startDelimPI)) |
1038 |
} else if (tryRead (startDelimComment)) { |
{ |
1039 |
parseComment (); |
parsePI(); |
1040 |
} else { |
} |
1041 |
return; |
else if (tryRead(startDelimComment)) |
1042 |
} |
{ |
1043 |
} |
parseComment(); |
1044 |
} |
} |
1045 |
|
else |
1046 |
|
{ |
1047 |
|
return; |
1048 |
|
} |
1049 |
|
} |
1050 |
|
} |
1051 |
|
|
1052 |
/** |
/** |
1053 |
* Parse a document type declaration. |
* Parse a document type declaration. |
1054 |
* <pre> |
* <pre> |
1055 |
* [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? |
* [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? |
1056 |
* ('[' (markupdecl | PEReference | S)* ']' S?)? '>' |
* ('[' (markupdecl | PEReference | S)* ']' S?)? '>' |
1057 |
* </pre> |
* </pre> |
1058 |
* <p> (The <code><!DOCTYPE</code> has already been read.) |
* <p> (The <code><!DOCTYPE</code> has already been read.) |
1059 |
*/ |
*/ |
1060 |
private void parseDoctypedecl () |
private void parseDoctypedecl() |
1061 |
throws Exception |
throws Exception |
1062 |
{ |
{ |
1063 |
String rootName, ids[]; |
String rootName; |
1064 |
|
ExternalIdentifiers ids; |
|
// Read the document type name. |
|
|
requireWhitespace (); |
|
|
rootName = readNmtoken (true); |
|
|
|
|
|
// Read the External subset's IDs |
|
|
skipWhitespace (); |
|
|
ids = readExternalIds (false, true); |
|
|
|
|
|
// report (a) declaration of name, (b) lexical info (ids) |
|
|
handler.doctypeDecl (rootName, ids [0], ids [1]); |
|
|
|
|
|
// Internal subset is parsed first, if present |
|
|
skipWhitespace (); |
|
|
if (tryRead ('[')) { |
|
|
|
|
|
// loop until the subset ends |
|
|
while (true) { |
|
|
doReport = expandPE = true; |
|
|
skipWhitespace (); |
|
|
doReport = expandPE = false; |
|
|
if (tryRead (']')) { |
|
|
break; // end of subset |
|
|
} else { |
|
|
// WFC, PEs in internal subset (only between decls) |
|
|
peIsError = expandPE = true; |
|
|
parseMarkupdecl (); |
|
|
peIsError = expandPE = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
skipWhitespace (); |
|
|
require ('>'); |
|
|
|
|
|
// Read the external subset, if any |
|
|
InputSource subset; |
|
|
|
|
|
if (ids [1] == null) |
|
|
subset = handler.getExternalSubset (rootName, |
|
|
handler.getSystemId ()); |
|
|
else |
|
|
subset = null; |
|
|
if (ids [1] != null || subset != null) { |
|
|
pushString (null, ">"); |
|
|
|
|
|
// NOTE: [dtd] is so we say what SAX2 expects, |
|
|
// though it's misleading (subset, not entire dtd) |
|
|
if (ids [1] != null) |
|
|
pushURL (true, "[dtd]", ids, null, null, null, true); |
|
|
else { |
|
|
handler.warn ("modifying document by adding external subset"); |
|
|
pushURL (true, "[dtd]", |
|
|
new String [] { subset.getPublicId (), |
|
|
subset.getSystemId (), null }, |
|
|
subset.getCharacterStream (), |
|
|
subset.getByteStream (), |
|
|
subset.getEncoding (), |
|
|
false); |
|
|
} |
|
|
|
|
|
// Loop until we end up back at '>' |
|
|
while (true) { |
|
|
doReport = expandPE = true; |
|
|
skipWhitespace (); |
|
|
doReport = expandPE = false; |
|
|
if (tryRead ('>')) { |
|
|
break; |
|
|
} else { |
|
|
expandPE = true; |
|
|
parseMarkupdecl (); |
|
|
expandPE = false; |
|
|
} |
|
|
} |
|
|
|
|
|
// the ">" string isn't popped yet |
|
|
if (inputStack.size () != 1) |
|
|
error ("external subset has unmatched '>'"); |
|
|
} |
|
|
|
|
|
// done dtd |
|
|
handler.endDoctype (); |
|
|
expandPE = false; |
|
|
doReport = true; |
|
|
} |
|
1065 |
|
|
1066 |
|
// Read the document type name. |
1067 |
|
requireWhitespace(); |
1068 |
|
rootName = readNmtoken(true); |
1069 |
|
|
1070 |
|
// Read the External subset's IDs |
1071 |
|
skipWhitespace(); |
1072 |
|
ids = readExternalIds(false, true); |
1073 |
|
|
1074 |
/** |
// report (a) declaration of name, (b) lexical info (ids) |
1075 |
* Parse a markup declaration in the internal or external DTD subset. |
handler.doctypeDecl(rootName, ids.publicId, ids.systemId); |
1076 |
* <pre> |
|
1077 |
* [29] markupdecl ::= elementdecl | Attlistdecl | EntityDecl |
// Internal subset is parsed first, if present |
1078 |
* | NotationDecl | PI | Comment |
skipWhitespace(); |
1079 |
* [30] extSubsetDecl ::= (markupdecl | conditionalSect |
if (tryRead('[')) |
1080 |
* | PEReference | S) * |
{ |
1081 |
* </pre> |
|
1082 |
* <p> Reading toplevel PE references is handled as a lexical issue |
// loop until the subset ends |
1083 |
* by the caller, as is whitespace. |
while (true) |
1084 |
*/ |
{ |
1085 |
private void parseMarkupdecl () |
doReport = expandPE = true; |
1086 |
|
skipWhitespace(); |
1087 |
|
doReport = expandPE = false; |
1088 |
|
if (tryRead(']')) |
1089 |
|
{ |
1090 |
|
break; // end of subset |
1091 |
|
} |
1092 |
|
else |
1093 |
|
{ |
1094 |
|
// WFC, PEs in internal subset (only between decls) |
1095 |
|
peIsError = expandPE = true; |
1096 |
|
parseMarkupdecl(); |
1097 |
|
peIsError = expandPE = false; |
1098 |
|
} |
1099 |
|
} |
1100 |
|
} |
1101 |
|
skipWhitespace(); |
1102 |
|
require('>'); |
1103 |
|
|
1104 |
|
// Read the external subset, if any |
1105 |
|
InputSource subset; |
1106 |
|
|
1107 |
|
if (ids.systemId == null) |
1108 |
|
{ |
1109 |
|
subset = handler.getExternalSubset(rootName, |
1110 |
|
handler.getSystemId()); |
1111 |
|
} |
1112 |
|
else |
1113 |
|
{ |
1114 |
|
subset = null; |
1115 |
|
} |
1116 |
|
if (ids.systemId != null || subset != null) |
1117 |
|
{ |
1118 |
|
pushString(null, ">"); |
1119 |
|
|
1120 |
|
// NOTE: [dtd] is so we say what SAX2 expects, |
1121 |
|
// though it's misleading (subset, not entire dtd) |
1122 |
|
if (ids.systemId != null) |
1123 |
|
{ |
1124 |
|
pushURL(true, "[dtd]", ids, null, null, null, true); |
1125 |
|
} |
1126 |
|
else |
1127 |
|
{ |
1128 |
|
handler.warn("modifying document by adding external subset"); |
1129 |
|
pushURL(true, "[dtd]", |
1130 |
|
new ExternalIdentifiers(subset.getPublicId(), |
1131 |
|
subset.getSystemId(), |
1132 |
|
null), |
1133 |
|
subset.getCharacterStream(), |
1134 |
|
subset.getByteStream(), |
1135 |
|
subset.getEncoding(), |
1136 |
|
false); |
1137 |
|
} |
1138 |
|
|
1139 |
|
// Loop until we end up back at '>' |
1140 |
|
while (true) |
1141 |
|
{ |
1142 |
|
doReport = expandPE = true; |
1143 |
|
skipWhitespace(); |
1144 |
|
doReport = expandPE = false; |
1145 |
|
if (tryRead('>')) |
1146 |
|
{ |
1147 |
|
break; |
1148 |
|
} |
1149 |
|
else |
1150 |
|
{ |
1151 |
|
expandPE = true; |
1152 |
|
parseMarkupdecl(); |
1153 |
|
expandPE = false; |
1154 |
|
} |
1155 |
|
} |
1156 |
|
|
1157 |
|
// the ">" string isn't popped yet |
1158 |
|
if (inputStack.size() != 1) |
1159 |
|
{ |
1160 |
|
error("external subset has unmatched '>'"); |
1161 |
|
} |
1162 |
|
} |
1163 |
|
|
1164 |
|
// done dtd |
1165 |
|
handler.endDoctype(); |
1166 |
|
expandPE = false; |
1167 |
|
doReport = true; |
1168 |
|
} |
1169 |
|
|
1170 |
|
/** |
1171 |
|
* Parse a markup declaration in the internal or external DTD subset. |
1172 |
|
* <pre> |
1173 |
|
* [29] markupdecl ::= elementdecl | Attlistdecl | EntityDecl |
1174 |
|
* | NotationDecl | PI | Comment |
1175 |
|
* [30] extSubsetDecl ::= (markupdecl | conditionalSect |
1176 |
|
* | PEReference | S) * |
1177 |
|
* </pre> |
1178 |
|
* <p> Reading toplevel PE references is handled as a lexical issue |
1179 |
|
* by the caller, as is whitespace. |
1180 |
|
*/ |
1181 |
|
private void parseMarkupdecl() |
1182 |
throws Exception |
throws Exception |
1183 |
{ |
{ |
1184 |
char saved [] = null; |
char[] saved = null; |
1185 |
boolean savedPE = expandPE; |
boolean savedPE = expandPE; |
|
|
|
|
// prevent "<%foo;" and ensures saved entity is right |
|
|
require ('<'); |
|
|
unread ('<'); |
|
|
expandPE = false; |
|
|
|
|
|
if (tryRead ("<!ELEMENT")) { |
|
|
saved = readBuffer; |
|
|
expandPE = savedPE; |
|
|
parseElementDecl (); |
|
|
} else if (tryRead ("<!ATTLIST")) { |
|
|
saved = readBuffer; |
|
|
expandPE = savedPE; |
|
|
parseAttlistDecl (); |
|
|
} else if (tryRead ("<!ENTITY")) { |
|
|
saved = readBuffer; |
|
|
expandPE = savedPE; |
|
|
parseEntityDecl (); |
|
|
} else if (tryRead ("<!NOTATION")) { |
|
|
saved = readBuffer; |
|
|
expandPE = savedPE; |
|
|
parseNotationDecl (); |
|
|
} else if (tryRead (startDelimPI)) { |
|
|
saved = readBuffer; |
|
|
expandPE = savedPE; |
|
|
parsePI (); |
|
|
} else if (tryRead (startDelimComment)) { |
|
|
saved = readBuffer; |
|
|
expandPE = savedPE; |
|
|
parseComment (); |
|
|
} else if (tryRead ("<![")) { |
|
|
saved = readBuffer; |
|
|
expandPE = savedPE; |
|
|
if (inputStack.size () > 0) |
|
|
parseConditionalSect (saved); |
|
|
else |
|
|
error ("conditional sections illegal in internal subset"); |
|
|
} else { |
|
|
error ("expected markup declaration"); |
|
|
} |
|
|
|
|
|
// VC: Proper Decl/PE Nesting |
|
|
if (readBuffer != saved) |
|
|
handler.verror ("Illegal Declaration/PE nesting"); |
|
|
} |
|
1186 |
|
|
1187 |
|
// prevent "<%foo;" and ensures saved entity is right |
1188 |
|
require('<'); |
1189 |
|
unread('<'); |
1190 |
|
expandPE = false; |
1191 |
|
|
1192 |
|
if (tryRead("<!ELEMENT")) |
1193 |
|
{ |
1194 |
|
saved = readBuffer; |
1195 |
|
expandPE = savedPE; |
1196 |
|
parseElementDecl(); |
1197 |
|
} |
1198 |
|
else if (tryRead("<!ATTLIST")) |
1199 |
|
{ |
1200 |
|
saved = readBuffer; |
1201 |
|
expandPE = savedPE; |
1202 |
|
parseAttlistDecl(); |
1203 |
|
} |
1204 |
|
else if (tryRead("<!ENTITY")) |
1205 |
|
{ |
1206 |
|
saved = readBuffer; |
1207 |
|
expandPE = savedPE; |
1208 |
|
parseEntityDecl(); |
1209 |
|
} |
1210 |
|
else if (tryRead("<!NOTATION")) |
1211 |
|
{ |
1212 |
|
saved = readBuffer; |
1213 |
|
expandPE = savedPE; |
1214 |
|
parseNotationDecl(); |
1215 |
|
} |
1216 |
|
else if (tryRead(startDelimPI)) |
1217 |
|
{ |
1218 |
|
saved = readBuffer; |
1219 |
|
expandPE = savedPE; |
1220 |
|
parsePI(); |
1221 |
|
} |
1222 |
|
else if (tryRead(startDelimComment)) |
1223 |
|
{ |
1224 |
|
saved = readBuffer; |
1225 |
|
expandPE = savedPE; |
1226 |
|
parseComment(); |
1227 |
|
} |
1228 |
|
else if (tryRead("<![")) |
1229 |
|
{ |
1230 |
|
saved = readBuffer; |
1231 |
|
expandPE = savedPE; |
1232 |
|
if (inputStack.size() > 0) |
1233 |
|
{ |
1234 |
|
parseConditionalSect(saved); |
1235 |
|
} |
1236 |
|
else |
1237 |
|
{ |
1238 |
|
error("conditional sections illegal in internal subset"); |
1239 |
|
} |
1240 |
|
} |
1241 |
|
else |
1242 |
|
{ |
1243 |
|
error("expected markup declaration"); |
1244 |
|
} |
1245 |
|
|
1246 |
/** |
// VC: Proper Decl/PE Nesting |
1247 |
* Parse an element, with its tags. |
if (readBuffer != saved) |
1248 |
* <pre> |
{ |
1249 |
* [39] element ::= EmptyElementTag | STag content ETag |
handler.verror("Illegal Declaration/PE nesting"); |
1250 |
* [40] STag ::= '<' Name (S Attribute)* S? '>' |
} |
1251 |
* [44] EmptyElementTag ::= '<' Name (S Attribute)* S? '/>' |
} |
1252 |
* </pre> |
|
1253 |
* <p> (The '<' has already been read.) |
/** |
1254 |
* <p>NOTE: this method actually chains onto parseContent (), if necessary, |
* Parse an element, with its tags. |
1255 |
* and parseContent () will take care of calling parseETag (). |
* <pre> |
1256 |
*/ |
* [39] element ::= EmptyElementTag | STag content ETag |
1257 |
private void parseElement (boolean maybeGetSubset) |
* [40] STag ::= '<' Name (S Attribute)* S? '>' |
1258 |
|
* [44] EmptyElementTag ::= '<' Name (S Attribute)* S? '/>' |
1259 |
|
* </pre> |
1260 |
|
* <p> (The '<' has already been read.) |
1261 |
|
* <p>NOTE: this method actually chains onto parseContent (), if necessary, |
1262 |
|
* and parseContent () will take care of calling parseETag (). |
1263 |
|
*/ |
1264 |
|
private void parseElement(boolean maybeGetSubset) |
1265 |
throws Exception |
throws Exception |
1266 |
{ |
{ |
1267 |
String gi; |
String gi; |
1268 |
char c; |
char c; |
1269 |
int oldElementContent = currentElementContent; |
int oldElementContent = currentElementContent; |
1270 |
String oldElement = currentElement; |
String oldElement = currentElement; |
1271 |
Object element []; |
ElementDecl element; |
1272 |
|
|
1273 |
// This is the (global) counter for the |
// This is the (global) counter for the |
1274 |
// array of specified attributes. |
// array of specified attributes. |
1275 |
tagAttributePos = 0; |
tagAttributePos = 0; |
1276 |
|
|
1277 |
// Read the element type name. |
// Read the element type name. |
1278 |
gi = readNmtoken (true); |
gi = readNmtoken(true); |
1279 |
|
|
1280 |
// If we saw no DTD, and this is the document root element, |
// If we saw no DTD, and this is the document root element, |
1281 |
// let the application modify the input stream by providing one. |
// let the application modify the input stream by providing one. |
1282 |
if (maybeGetSubset) { |
if (maybeGetSubset) |
1283 |
InputSource subset = handler.getExternalSubset (gi, |
{ |
1284 |
handler.getSystemId ()); |
InputSource subset = handler.getExternalSubset(gi, |
1285 |
if (subset != null) { |
handler.getSystemId()); |
1286 |
String publicId = subset.getPublicId (); |
if (subset != null) |
1287 |
String systemId = subset.getSystemId (); |
{ |
1288 |
|
String publicId = subset.getPublicId(); |
1289 |
handler.warn ("modifying document by adding DTD"); |
String systemId = subset.getSystemId(); |
1290 |
handler.doctypeDecl (gi, publicId, systemId); |
|
1291 |
pushString (null, ">"); |
handler.warn("modifying document by adding DTD"); |
1292 |
|
handler.doctypeDecl(gi, publicId, systemId); |
1293 |
// NOTE: [dtd] is so we say what SAX2 expects, |
pushString(null, ">"); |
1294 |
// though it's misleading (subset, not entire dtd) |
|
1295 |
pushURL (true, "[dtd]", |
// NOTE: [dtd] is so we say what SAX2 expects, |
1296 |
new String [] { publicId, systemId, null }, |
// though it's misleading (subset, not entire dtd) |
1297 |
subset.getCharacterStream (), |
pushURL(true, "[dtd]", |
1298 |
subset.getByteStream (), |
new ExternalIdentifiers(publicId, systemId, null), |
1299 |
subset.getEncoding (), |
subset.getCharacterStream(), |
1300 |
false); |
subset.getByteStream(), |
1301 |
|
subset.getEncoding(), |
1302 |
// Loop until we end up back at '>' |
false); |
1303 |
while (true) { |
|
1304 |
doReport = expandPE = true; |
// Loop until we end up back at '>' |
1305 |
skipWhitespace (); |
while (true) |
1306 |
doReport = expandPE = false; |
{ |
1307 |
if (tryRead ('>')) { |
doReport = expandPE = true; |
1308 |
break; |
skipWhitespace(); |
1309 |
} else { |
doReport = expandPE = false; |
1310 |
expandPE = true; |
if (tryRead('>')) |
1311 |
parseMarkupdecl (); |
{ |
1312 |
expandPE = false; |
break; |
1313 |
} |
} |
1314 |
} |
else |
1315 |
|
{ |
1316 |
// the ">" string isn't popped yet |
expandPE = true; |
1317 |
if (inputStack.size () != 1) |
parseMarkupdecl(); |
1318 |
error ("external subset has unmatched '>'"); |
expandPE = false; |
1319 |
|
} |
1320 |
handler.endDoctype (); |
} |
1321 |
} |
|
1322 |
} |
// the ">" string isn't popped yet |
1323 |
|
if (inputStack.size() != 1) |
1324 |
// Determine the current content type. |
{ |
1325 |
currentElement = gi; |
error("external subset has unmatched '>'"); |
1326 |
element = (Object []) elementInfo.get (gi); |
} |
1327 |
currentElementContent = getContentType (element, CONTENT_ANY); |
|
1328 |
|
handler.endDoctype(); |
1329 |
// Read the attributes, if any. |
} |
1330 |
// After this loop, "c" is the closing delimiter. |
} |
1331 |
boolean white = tryWhitespace (); |
|
1332 |
c = readCh (); |
// Determine the current content type. |
1333 |
while (c != '/' && c != '>') { |
currentElement = gi; |
1334 |
unread (c); |
element = (ElementDecl) elementInfo.get(gi); |
1335 |
if (!white) |
currentElementContent = getContentType(element, CONTENT_ANY); |
1336 |
error ("need whitespace between attributes"); |
|
1337 |
parseAttribute (gi); |
// Read the attributes, if any. |
1338 |
white = tryWhitespace (); |
// After this loop, "c" is the closing delimiter. |
1339 |
c = readCh (); |
boolean white = tryWhitespace(); |
1340 |
} |
c = readCh(); |
1341 |
|
while (c != '/' && c != '>') |
1342 |
// Supply any defaulted attributes. |
{ |
1343 |
Enumeration atts = declaredAttributes (element); |
unread(c); |
1344 |
if (atts != null) { |
if (!white) |
1345 |
String aname; |
{ |
1346 |
|
error("need whitespace between attributes"); |
1347 |
|
} |
1348 |
|
parseAttribute(gi); |
1349 |
|
white = tryWhitespace(); |
1350 |
|
c = readCh(); |
1351 |
|
} |
1352 |
|
|
1353 |
|
// Supply any defaulted attributes. |
1354 |
|
Iterator atts = declaredAttributes(element); |
1355 |
|
if (atts != null) |
1356 |
|
{ |
1357 |
|
String aname; |
1358 |
loop: |
loop: |
1359 |
while (atts.hasMoreElements ()) { |
while (atts.hasNext()) |
1360 |
aname = (String) atts.nextElement (); |
{ |
1361 |
// See if it was specified. |
aname = (String) atts.next(); |
1362 |
for (int i = 0; i < tagAttributePos; i++) { |
// See if it was specified. |
1363 |
if (tagAttributes [i] == aname) { |
for (int i = 0; i < tagAttributePos; i++) |
1364 |
continue loop; |
{ |
1365 |
} |
if (tagAttributes[i] == aname) |
1366 |
} |
{ |
1367 |
// ... or has a default |
continue loop; |
1368 |
String value = getAttributeDefaultValue (gi, aname); |
} |
1369 |
|
} |
1370 |
if (value == null) |
// ... or has a default |
1371 |
continue; |
String value = getAttributeDefaultValue(gi, aname); |
1372 |
handler.attribute (aname, value, false); |
|
1373 |
} |
if (value == null) |
1374 |
} |
{ |
1375 |
|
continue; |
1376 |
// Figure out if this is a start tag |
} |
1377 |
// or an empty element, and dispatch an |
handler.attribute(aname, value, false); |
1378 |
// event accordingly. |
} |
1379 |
switch (c) { |
} |
|
case '>': |
|
|
handler.startElement (gi); |
|
|
parseContent (); |
|
|
break; |
|
|
case '/': |
|
|
require ('>'); |
|
|
handler.startElement (gi); |
|
|
handler.endElement (gi); |
|
|
break; |
|
|
} |
|
|
|
|
|
// Restore the previous state. |
|
|
currentElement = oldElement; |
|
|
currentElementContent = oldElementContent; |
|
|
} |
|
1380 |
|
|
1381 |
|
// Figure out if this is a start tag |
1382 |
|
// or an empty element, and dispatch an |
1383 |
|
// event accordingly. |
1384 |
|
switch (c) |
1385 |
|
{ |
1386 |
|
case '>': |
1387 |
|
handler.startElement(gi); |
1388 |
|
parseContent(); |
1389 |
|
break; |
1390 |
|
case '/': |
1391 |
|
require('>'); |
1392 |
|
handler.startElement(gi); |
1393 |
|
handler.endElement(gi); |
1394 |
|
break; |
1395 |
|
} |
1396 |
|
|
1397 |
/** |
// Restore the previous state. |
1398 |
* Parse an attribute assignment. |
currentElement = oldElement; |
1399 |
* <pre> |
currentElementContent = oldElementContent; |
|
* [41] Attribute ::= Name Eq AttValue |
|
|
* </pre> |
|
|
* @param name The name of the attribute's element. |
|
|
* @see SAXDriver#attribute |
|
|
*/ |
|
|
private void parseAttribute (String name) |
|
|
throws Exception |
|
|
{ |
|
|
String aname; |
|
|
String type; |
|
|
String value; |
|
|
int flags = LIT_ATTRIBUTE | LIT_ENTITY_REF; |
|
|
|
|
|
// Read the attribute name. |
|
|
aname = readNmtoken (true); |
|
|
type = getAttributeType (name, aname); |
|
|
|
|
|
// Parse '=' |
|
|
parseEq (); |
|
|
|
|
|
// Read the value, normalizing whitespace |
|
|
// unless it is CDATA. |
|
|
if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { |
|
|
if (type == "CDATA" || type == null) { |
|
|
value = readLiteral (flags); |
|
|
} else { |
|
|
value = readLiteral (flags | LIT_NORMALIZE); |
|
|
} |
|
|
} else { |
|
|
if (type.equals("CDATA") || type == null) { |
|
|
value = readLiteral (flags); |
|
|
} else { |
|
|
value = readLiteral (flags | LIT_NORMALIZE); |
|
|
} |
|
1400 |
} |
} |
1401 |
|
|
1402 |
|
/** |
1403 |
|
* Parse an attribute assignment. |
1404 |
|
* <pre> |
1405 |
|
* [41] Attribute ::= Name Eq AttValue |
1406 |
|
* </pre> |
1407 |
|
* @param name The name of the attribute's element. |
1408 |
|
* @see SAXDriver#attribute |
1409 |
|
*/ |
1410 |
|
private void parseAttribute(String name) |
1411 |
|
throws Exception |
1412 |
|
{ |
1413 |
|
String aname; |
1414 |
|
String type; |
1415 |
|
String value; |
1416 |
|
int flags = LIT_ATTRIBUTE | LIT_ENTITY_REF; |
1417 |
|
|
1418 |
|
// Read the attribute name. |
1419 |
|
aname = readNmtoken(true); |
1420 |
|
type = getAttributeType(name, aname); |
1421 |
|
|
1422 |
|
// Parse '=' |
1423 |
|
parseEq(); |
1424 |
|
|
1425 |
// WFC: no duplicate attributes |
// Read the value, normalizing whitespace |
1426 |
for (int i = 0; i < tagAttributePos; i++) |
// unless it is CDATA. |
1427 |
if (aname.equals (tagAttributes [i])) |
if (handler.stringInterning) |
1428 |
error ("duplicate attribute", aname, null); |
{ |
1429 |
|
if (type == "CDATA" || type == null) |
1430 |
// Inform the handler about the |
{ |
1431 |
// attribute. |
value = readLiteral(flags); |
1432 |
handler.attribute (aname, value, true); |
} |
1433 |
dataBufferPos = 0; |
else |
1434 |
|
{ |
1435 |
// Note that the attribute has been |
value = readLiteral(flags | LIT_NORMALIZE); |
1436 |
// specified. |
} |
1437 |
if (tagAttributePos == tagAttributes.length) { |
} |
1438 |
String newAttrib[] = new String [tagAttributes.length * 2]; |
else |
1439 |
System.arraycopy (tagAttributes, 0, newAttrib, 0, tagAttributePos); |
{ |
1440 |
tagAttributes = newAttrib; |
if (type.equals("CDATA") || type == null) |
1441 |
} |
{ |
1442 |
tagAttributes [tagAttributePos++] = aname; |
value = readLiteral(flags); |
1443 |
} |
} |
1444 |
|
else |
1445 |
|
{ |
1446 |
|
value = readLiteral(flags | LIT_NORMALIZE); |
1447 |
|
} |
1448 |
|
} |
1449 |
|
|
1450 |
|
// WFC: no duplicate attributes |
1451 |
|
for (int i = 0; i < tagAttributePos; i++) |
1452 |
|
{ |
1453 |
|
if (aname.equals(tagAttributes [i])) |
1454 |
|
{ |
1455 |
|
error("duplicate attribute", aname, null); |
1456 |
|
} |
1457 |
|
} |
1458 |
|
|
1459 |
/** |
// Inform the handler about the |
1460 |
* Parse an equals sign surrounded by optional whitespace. |
// attribute. |
1461 |
* <pre> |
handler.attribute(aname, value, true); |
1462 |
* [25] Eq ::= S? '=' S? |
dataBufferPos = 0; |
1463 |
* </pre> |
|
1464 |
*/ |
// Note that the attribute has been |
1465 |
private void parseEq () |
// specified. |
1466 |
throws SAXException, IOException |
if (tagAttributePos == tagAttributes.length) |
1467 |
{ |
{ |
1468 |
skipWhitespace (); |
String newAttrib[] = new String[tagAttributes.length * 2]; |
1469 |
require ('='); |
System.arraycopy(tagAttributes, 0, newAttrib, 0, tagAttributePos); |
1470 |
skipWhitespace (); |
tagAttributes = newAttrib; |
1471 |
} |
} |
1472 |
|
tagAttributes[tagAttributePos++] = aname; |
1473 |
|
} |
1474 |
|
|
1475 |
|
/** |
1476 |
|
* Parse an equals sign surrounded by optional whitespace. |
1477 |
|
* <pre> |
1478 |
|
* [25] Eq ::= S? '=' S? |
1479 |
|
* </pre> |
1480 |
|
*/ |
1481 |
|
private void parseEq() |
1482 |
|
throws SAXException, IOException |
1483 |
|
{ |
1484 |
|
skipWhitespace(); |
1485 |
|
require('='); |
1486 |
|
skipWhitespace(); |
1487 |
|
} |
1488 |
|
|
1489 |
/** |
/** |
1490 |
* Parse an end tag. |
* Parse an end tag. |
1491 |
* <pre> |
* <pre> |
1492 |
* [42] ETag ::= '</' Name S? '>' |
* [42] ETag ::= '</' Name S? '>' |
1493 |
* </pre> |
* </pre> |
1494 |
* <p>NOTE: parseContent () chains to here, we already read the |
* <p>NOTE: parseContent () chains to here, we already read the |
1495 |
* "</". |
* "</". |
1496 |
*/ |
*/ |
1497 |
private void parseETag () |
private void parseETag() |
1498 |
throws Exception |
throws Exception |
1499 |
{ |
{ |
1500 |
require (currentElement); |
require(currentElement); |
1501 |
skipWhitespace (); |
skipWhitespace(); |
1502 |
require ('>'); |
require('>'); |
1503 |
handler.endElement (currentElement); |
handler.endElement(currentElement); |
1504 |
// not re-reporting any SAXException re bogus end tags, |
// not re-reporting any SAXException re bogus end tags, |
1505 |
// even though that diagnostic might be clearer ... |
// even though that diagnostic might be clearer ... |
1506 |
} |
} |
1507 |
|
|
1508 |
|
/** |
1509 |
/** |
* Parse the content of an element. |
1510 |
* Parse the content of an element. |
* <pre> |
1511 |
* <pre> |
* [43] content ::= (element | CharData | Reference |
1512 |
* [43] content ::= (element | CharData | Reference |
* | CDSect | PI | Comment)* |
1513 |
* | CDSect | PI | Comment)* |
* [67] Reference ::= EntityRef | CharRef |
1514 |
* [67] Reference ::= EntityRef | CharRef |
* </pre> |
1515 |
* </pre> |
* <p> NOTE: consumes ETtag. |
1516 |
* <p> NOTE: consumes ETtag. |
*/ |
1517 |
*/ |
private void parseContent() |
|
private void parseContent () |
|
1518 |
throws Exception |
throws Exception |
1519 |
{ |
{ |
1520 |
char c; |
char c; |
1521 |
|
|
1522 |
while (true) { |
while (true) |
1523 |
// consume characters (or ignorable whitspace) until delimiter |
{ |
1524 |
parseCharData (); |
// consume characters (or ignorable whitspace) until delimiter |
1525 |
|
parseCharData(); |
1526 |
// Handle delimiters |
|
1527 |
c = readCh (); |
// Handle delimiters |
1528 |
switch (c) { |
c = readCh(); |
1529 |
|
switch (c) |
1530 |
case '&': // Found "&" |
{ |
1531 |
c = readCh (); |
case '&': // Found "&" |
1532 |
if (c == '#') { |
c = readCh(); |
1533 |
parseCharRef (); |
if (c == '#') |
1534 |
} else { |
{ |
1535 |
unread (c); |
parseCharRef(); |
1536 |
parseEntityRef (true); |
} |
1537 |
} |
else |
1538 |
isDirtyCurrentElement = true; |
{ |
1539 |
break; |
unread(c); |
1540 |
|
parseEntityRef(true); |
1541 |
case '<': // Found "<" |
} |
1542 |
dataBufferFlush (); |
isDirtyCurrentElement = true; |
1543 |
c = readCh (); |
break; |
1544 |
switch (c) { |
|
1545 |
case '!': // Found "<!" |
case '<': // Found "<" |
1546 |
c = readCh (); |
dataBufferFlush(); |
1547 |
switch (c) { |
c = readCh(); |
1548 |
case '-': // Found "<!-" |
switch (c) |
1549 |
require ('-'); |
{ |
1550 |
isDirtyCurrentElement = false; |
case '!': // Found "<!" |
1551 |
parseComment (); |
c = readCh(); |
1552 |
break; |
switch (c) |
1553 |
case '[': // Found "<![" |
{ |
1554 |
isDirtyCurrentElement = false; |
case '-': // Found "<!-" |
1555 |
require ("CDATA["); |
require('-'); |
1556 |
handler.startCDATA (); |
isDirtyCurrentElement = false; |
1557 |
inCDATA = true; |
parseComment(); |
1558 |
parseCDSect (); |
break; |
1559 |
inCDATA = false; |
case '[': // Found "<![" |
1560 |
handler.endCDATA (); |
isDirtyCurrentElement = false; |
1561 |
break; |
require("CDATA["); |
1562 |
default: |
handler.startCDATA(); |
1563 |
error ("expected comment or CDATA section", c, null); |
inCDATA = true; |
1564 |
break; |
parseCDSect(); |
1565 |
} |
inCDATA = false; |
1566 |
break; |
handler.endCDATA(); |
1567 |
|
break; |
1568 |
case '?': // Found "<?" |
default: |
1569 |
isDirtyCurrentElement = false; |
error("expected comment or CDATA section", c, null); |
1570 |
parsePI (); |
break; |
1571 |
break; |
} |
1572 |
|
break; |
1573 |
case '/': // Found "</" |
|
1574 |
isDirtyCurrentElement = false; |
case '?': // Found "<?" |
1575 |
parseETag (); |
isDirtyCurrentElement = false; |
1576 |
return; |
parsePI(); |
1577 |
|
break; |
1578 |
default: // Found "<" followed by something else |
|
1579 |
isDirtyCurrentElement = false; |
case '/': // Found "</" |
1580 |
unread (c); |
isDirtyCurrentElement = false; |
1581 |
parseElement (false); |
parseETag(); |
1582 |
break; |
return; |
1583 |
} |
|
1584 |
} |
default: // Found "<" followed by something else |
1585 |
} |
isDirtyCurrentElement = false; |
1586 |
|
unread(c); |
1587 |
} |
parseElement(false); |
1588 |
|
break; |
1589 |
|
} |
1590 |
/** |
} |
1591 |
* Parse an element type declaration. |
} |
1592 |
* <pre> |
} |
1593 |
* [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' |
|
1594 |
* </pre> |
/** |
1595 |
* <p> NOTE: the '<!ELEMENT' has already been read. |
* Parse an element type declaration. |
1596 |
*/ |
* <pre> |
1597 |
private void parseElementDecl () |
* [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' |
1598 |
|
* </pre> |
1599 |
|
* <p> NOTE: the '<!ELEMENT' has already been read. |
1600 |
|
*/ |
1601 |
|
private void parseElementDecl() |
1602 |
throws Exception |
throws Exception |
1603 |
{ |
{ |
1604 |
String name; |
String name; |
1605 |
|
|
1606 |
requireWhitespace (); |
requireWhitespace(); |
1607 |
// Read the element type name. |
// Read the element type name. |
1608 |
name = readNmtoken (true); |
name = readNmtoken(true); |
1609 |
|
|
1610 |
requireWhitespace (); |
requireWhitespace(); |
1611 |
// Read the content model. |
// Read the content model. |
1612 |
parseContentspec (name); |
parseContentspec(name); |
1613 |
|
|
1614 |
skipWhitespace (); |
skipWhitespace(); |
1615 |
require ('>'); |
require('>'); |
1616 |
} |
} |
|
|
|
1617 |
|
|
1618 |
/** |
/** |
1619 |
* Content specification. |
* Content specification. |
1620 |
* <pre> |
* <pre> |
1621 |
* [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | elements |
* [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | elements |
1622 |
* </pre> |
* </pre> |
1623 |
*/ |
*/ |
1624 |
private void parseContentspec (String name) |
private void parseContentspec(String name) |
1625 |
throws Exception |
throws Exception |
1626 |
{ |
{ |
1627 |
// FIXME: move elementDecl() into setElement(), pass EMTPY/ANY ... |
// FIXME: move elementDecl() into setElement(), pass EMTPY/ANY ... |
1628 |
if (tryRead ("EMPTY")) { |
if (tryRead("EMPTY")) |
1629 |
setElement (name, CONTENT_EMPTY, null, null); |
{ |
1630 |
if (!skippedPE) |
setElement(name, CONTENT_EMPTY, null, null); |
1631 |
handler.getDeclHandler ().elementDecl (name, "EMPTY"); |
if (!skippedPE) |
1632 |
return; |
{ |
1633 |
} else if (tryRead ("ANY")) { |
handler.getDeclHandler().elementDecl(name, "EMPTY"); |
1634 |
setElement (name, CONTENT_ANY, null, null); |
} |
1635 |
if (!skippedPE) |
return; |
1636 |
handler.getDeclHandler ().elementDecl (name, "ANY"); |
} |
1637 |
return; |
else if (tryRead("ANY")) |
1638 |
} else { |
{ |
1639 |
String model; |
setElement(name, CONTENT_ANY, null, null); |
1640 |
char saved []; |
if (!skippedPE) |
1641 |
|
{ |
1642 |
require ('('); |
handler.getDeclHandler().elementDecl(name, "ANY"); |
1643 |
saved = readBuffer; |
} |
1644 |
dataBufferAppend ('('); |
return; |
1645 |
skipWhitespace (); |
} |
1646 |
if (tryRead ("#PCDATA")) { |
else |
1647 |
dataBufferAppend ("#PCDATA"); |
{ |
1648 |
parseMixed (saved); |
String model; |
1649 |
model = dataBufferToString (); |
char[] saved; |
1650 |
setElement (name, CONTENT_MIXED, model, null); |
|
1651 |
} else { |
require('('); |
1652 |
parseElements (saved); |
saved = readBuffer; |
1653 |
model = dataBufferToString (); |
dataBufferAppend('('); |
1654 |
setElement (name, CONTENT_ELEMENTS, model, null); |
skipWhitespace(); |
1655 |
} |
if (tryRead("#PCDATA")) |
1656 |
if (!skippedPE) |
{ |
1657 |
handler.getDeclHandler ().elementDecl (name, model); |
dataBufferAppend("#PCDATA"); |
1658 |
} |
parseMixed(saved); |
1659 |
} |
model = dataBufferToString(); |
1660 |
|
setElement(name, CONTENT_MIXED, model, null); |
1661 |
/** |
} |
1662 |
* Parse an element-content model. |
else |
1663 |
* <pre> |
{ |
1664 |
* [47] elements ::= (choice | seq) ('?' | '*' | '+')? |
parseElements(saved); |
1665 |
* [49] choice ::= '(' S? cp (S? '|' S? cp)+ S? ')' |
model = dataBufferToString(); |
1666 |
* [50] seq ::= '(' S? cp (S? ',' S? cp)* S? ')' |
setElement(name, CONTENT_ELEMENTS, model, null); |
1667 |
* </pre> |
} |
1668 |
* |
if (!skippedPE) |
1669 |
* <p> NOTE: the opening '(' and S have already been read. |
{ |
1670 |
* |
handler.getDeclHandler().elementDecl(name, model); |
1671 |
* @param saved Buffer for entity that should have the terminal ')' |
} |
1672 |
*/ |
} |
1673 |
private void parseElements (char saved []) |
} |
1674 |
|
|
1675 |
|
/** |
1676 |
|
* Parse an element-content model. |
1677 |
|
* <pre> |
1678 |
|
* [47] elements ::= (choice | seq) ('?' | '*' | '+')? |
1679 |
|
* [49] choice ::= '(' S? cp (S? '|' S? cp)+ S? ')' |
1680 |
|
* [50] seq ::= '(' S? cp (S? ',' S? cp)* S? ')' |
1681 |
|
* </pre> |
1682 |
|
* |
1683 |
|
* <p> NOTE: the opening '(' and S have already been read. |
1684 |
|
* |
1685 |
|
* @param saved Buffer for entity that should have the terminal ')' |
1686 |
|
*/ |
1687 |
|
private void parseElements(char[] saved) |
1688 |
throws Exception |
throws Exception |
1689 |
{ |
{ |
1690 |
char c; |
char c; |
1691 |
char sep; |
char sep; |
1692 |
|
|
1693 |
// Parse the first content particle |
// Parse the first content particle |
1694 |
skipWhitespace (); |
skipWhitespace(); |
1695 |
parseCp (); |
parseCp(); |
1696 |
|
|
1697 |
// Check for end or for a separator. |
// Check for end or for a separator. |
1698 |
skipWhitespace (); |
skipWhitespace(); |
1699 |
c = readCh (); |
c = readCh(); |
1700 |
switch (c) { |
switch (c) |
1701 |
case ')': |
{ |
1702 |
// VC: Proper Group/PE Nesting |
case ')': |
1703 |
if (readBuffer != saved) |
// VC: Proper Group/PE Nesting |
1704 |
handler.verror ("Illegal Group/PE nesting"); |
if (readBuffer != saved) |
1705 |
|
{ |
1706 |
dataBufferAppend (')'); |
handler.verror("Illegal Group/PE nesting"); |
1707 |
c = readCh (); |
} |
1708 |
switch (c) { |
|
1709 |
case '*': |
dataBufferAppend(')'); |
1710 |
case '+': |
c = readCh(); |
1711 |
case '?': |
switch (c) |
1712 |
dataBufferAppend (c); |
{ |
1713 |
break; |
case '*': |
1714 |
default: |
case '+': |
1715 |
unread (c); |
case '?': |
1716 |
} |
dataBufferAppend(c); |
1717 |
return; |
break; |
1718 |
case ',': // Register the separator. |
default: |
1719 |
case '|': |
unread(c); |
1720 |
sep = c; |
} |
1721 |
dataBufferAppend (c); |
return; |
1722 |
break; |
case ',': // Register the separator. |
1723 |
default: |
case '|': |
1724 |
error ("bad separator in content model", c, null); |
sep = c; |
1725 |
return; |
dataBufferAppend(c); |
1726 |
} |
break; |
1727 |
|
default: |
1728 |
// Parse the rest of the content model. |
error("bad separator in content model", c, null); |
1729 |
while (true) { |
return; |
1730 |
skipWhitespace (); |
} |
1731 |
parseCp (); |
|
1732 |
skipWhitespace (); |
// Parse the rest of the content model. |
1733 |
c = readCh (); |
while (true) |
1734 |
if (c == ')') { |
{ |
1735 |
// VC: Proper Group/PE Nesting |
skipWhitespace(); |
1736 |
if (readBuffer != saved) |
parseCp(); |
1737 |
handler.verror ("Illegal Group/PE nesting"); |
skipWhitespace(); |
1738 |
|
c = readCh(); |
1739 |
dataBufferAppend (')'); |
if (c == ')') |
1740 |
break; |
{ |
1741 |
} else if (c != sep) { |
// VC: Proper Group/PE Nesting |
1742 |
error ("bad separator in content model", c, null); |
if (readBuffer != saved) |
1743 |
return; |
{ |
1744 |
} else { |
handler.verror("Illegal Group/PE nesting"); |
1745 |
dataBufferAppend (c); |
} |
1746 |
} |
|
1747 |
} |
dataBufferAppend(')'); |
1748 |
|
break; |
1749 |
// Check for the occurrence indicator. |
} |
1750 |
c = readCh (); |
else if (c != sep) |
1751 |
switch (c) { |
{ |
1752 |
case '?': |
error("bad separator in content model", c, null); |
1753 |
case '*': |
return; |
1754 |
case '+': |
} |
1755 |
dataBufferAppend (c); |
else |
1756 |
return; |
{ |
1757 |
default: |
dataBufferAppend(c); |
1758 |
unread (c); |
} |
1759 |
return; |
} |
1760 |
} |
|
1761 |
} |
// Check for the occurrence indicator. |
1762 |
|
c = readCh(); |
1763 |
|
switch (c) |
1764 |
/** |
{ |
1765 |
* Parse a content particle. |
case '?': |
1766 |
* <pre> |
case '*': |
1767 |
* [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')? |
case '+': |
1768 |
* </pre> |
dataBufferAppend(c); |
1769 |
*/ |
return; |
1770 |
private void parseCp () |
default: |
1771 |
|
unread(c); |
1772 |
|
return; |
1773 |
|
} |
1774 |
|
} |
1775 |
|
|
1776 |
|
/** |
1777 |
|
* Parse a content particle. |
1778 |
|
* <pre> |
1779 |
|
* [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')? |
1780 |
|
* </pre> |
1781 |
|
*/ |
1782 |
|
private void parseCp() |
1783 |
throws Exception |
throws Exception |
1784 |
{ |
{ |
1785 |
if (tryRead ('(')) { |
if (tryRead('(')) |
1786 |
dataBufferAppend ('('); |
{ |
1787 |
parseElements (readBuffer); |
dataBufferAppend('('); |
1788 |
} else { |
parseElements(readBuffer); |
1789 |
dataBufferAppend (readNmtoken (true)); |
} |
1790 |
char c = readCh (); |
else |
1791 |
switch (c) { |
{ |
1792 |
case '?': |
dataBufferAppend(readNmtoken(true)); |
1793 |
case '*': |
char c = readCh(); |
1794 |
case '+': |
switch (c) |
1795 |
dataBufferAppend (c); |
{ |
1796 |
break; |
case '?': |
1797 |
default: |
case '*': |
1798 |
unread (c); |
case '+': |
1799 |
break; |
dataBufferAppend(c); |
1800 |
} |
break; |
1801 |
} |
default: |
1802 |
} |
unread(c); |
1803 |
|
break; |
1804 |
|
} |
1805 |
|
} |
1806 |
|
} |
1807 |
|
|
1808 |
/** |
/** |
1809 |
* Parse mixed content. |
* Parse mixed content. |
1810 |
* <pre> |
* <pre> |
1811 |
* [51] Mixed ::= '(' S? ( '#PCDATA' (S? '|' S? Name)*) S? ')*' |
* [51] Mixed ::= '(' S? ( '#PCDATA' (S? '|' S? Name)*) S? ')*' |
1812 |
* | '(' S? ('#PCDATA') S? ')' |
* | '(' S? ('#PCDATA') S? ')' |
1813 |
* </pre> |
* </pre> |
1814 |
* |
* |
1815 |
* @param saved Buffer for entity that should have the terminal ')' |
* @param saved Buffer for entity that should have the terminal ')' |
1816 |
*/ |
*/ |
1817 |
private void parseMixed (char saved []) |
private void parseMixed(char[] saved) |
1818 |
throws Exception |
throws Exception |
1819 |
{ |
{ |
1820 |
// Check for PCDATA alone. |
// Check for PCDATA alone. |
1821 |
skipWhitespace (); |
skipWhitespace(); |
1822 |
if (tryRead (')')) { |
if (tryRead(')')) |
1823 |
// VC: Proper Group/PE Nesting |
{ |
1824 |
if (readBuffer != saved) |
// VC: Proper Group/PE Nesting |
1825 |
handler.verror ("Illegal Group/PE nesting"); |
if (readBuffer != saved) |
1826 |
|
{ |
1827 |
dataBufferAppend (")*"); |
handler.verror("Illegal Group/PE nesting"); |
1828 |
tryRead ('*'); |
} |
1829 |
return; |
|
1830 |
} |
dataBufferAppend(")*"); |
1831 |
|
tryRead('*'); |
1832 |
// Parse mixed content. |
return; |
1833 |
skipWhitespace (); |
} |
1834 |
while (!tryRead (")")) { |
|
1835 |
require ('|'); |
// Parse mixed content. |
1836 |
dataBufferAppend ('|'); |
skipWhitespace(); |
1837 |
skipWhitespace (); |
while (!tryRead(")")) |
1838 |
dataBufferAppend (readNmtoken (true)); |
{ |
1839 |
skipWhitespace (); |
require('|'); |
1840 |
} |
dataBufferAppend('|'); |
1841 |
|
skipWhitespace(); |
1842 |
// VC: Proper Group/PE Nesting |
dataBufferAppend(readNmtoken(true)); |
1843 |
if (readBuffer != saved) |
skipWhitespace(); |
1844 |
handler.verror ("Illegal Group/PE nesting"); |
} |
1845 |
|
|
1846 |
require ('*'); |
// VC: Proper Group/PE Nesting |
1847 |
dataBufferAppend (")*"); |
if (readBuffer != saved) |
1848 |
} |
{ |
1849 |
|
handler.verror("Illegal Group/PE nesting"); |
1850 |
|
} |
1851 |
/** |
|
1852 |
* Parse an attribute list declaration. |
require('*'); |
1853 |
* <pre> |
dataBufferAppend(")*"); |
1854 |
* [52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>' |
} |
1855 |
* </pre> |
|
1856 |
* <p>NOTE: the '<!ATTLIST' has already been read. |
/** |
1857 |
*/ |
* Parse an attribute list declaration. |
1858 |
private void parseAttlistDecl () |
* <pre> |
1859 |
|
* [52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>' |
1860 |
|
* </pre> |
1861 |
|
* <p>NOTE: the '<!ATTLIST' has already been read. |
1862 |
|
*/ |
1863 |
|
private void parseAttlistDecl() |
1864 |
throws Exception |
throws Exception |
1865 |
{ |
{ |
1866 |
String elementName; |
String elementName; |
1867 |
|
|
1868 |
requireWhitespace (); |
requireWhitespace(); |
1869 |
elementName = readNmtoken (true); |
elementName = readNmtoken(true); |
1870 |
boolean white = tryWhitespace (); |
boolean white = tryWhitespace(); |
1871 |
while (!tryRead ('>')) { |
while (!tryRead('>')) |
1872 |
if (!white) |
{ |
1873 |
error ("whitespace required before attribute definition"); |
if (!white) |
1874 |
parseAttDef (elementName); |
{ |
1875 |
white = tryWhitespace (); |
error("whitespace required before attribute definition"); |
1876 |
} |
} |
1877 |
} |
parseAttDef(elementName); |
1878 |
|
white = tryWhitespace(); |
1879 |
|
} |
1880 |
/** |
} |
1881 |
* Parse a single attribute definition. |
|
1882 |
* <pre> |
/** |
1883 |
* [53] AttDef ::= S Name S AttType S DefaultDecl |
* Parse a single attribute definition. |
1884 |
* </pre> |
* <pre> |
1885 |
*/ |
* [53] AttDef ::= S Name S AttType S DefaultDecl |
1886 |
private void parseAttDef (String elementName) |
* </pre> |
1887 |
|
*/ |
1888 |
|
private void parseAttDef(String elementName) |
1889 |
throws Exception |
throws Exception |
1890 |
{ |
{ |
1891 |
String name; |
String name; |
1892 |
String type; |
String type; |
1893 |
String enumer = null; |
String enumer = null; |
1894 |
|
|
1895 |
// Read the attribute name. |
// Read the attribute name. |
1896 |
name = readNmtoken (true); |
name = readNmtoken(true); |
|
|
|
|
// Read the attribute type. |
|
|
requireWhitespace (); |
|
|
type = readAttType (); |
|
|
|
|
|
// Get the string of enumerated values if necessary. |
|
|
if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { |
|
|
if ("ENUMERATION" == type || "NOTATION" == type) |
|
|
enumer = dataBufferToString (); |
|
|
} else { |
|
|
if ("ENUMERATION".equals(type) || "NOTATION".equals(type)) |
|
|
enumer = dataBufferToString (); |
|
|
} |
|
|
|
|
|
// Read the default value. |
|
|
requireWhitespace (); |
|
|
parseDefault (elementName, name, type, enumer); |
|
|
} |
|
1897 |
|
|
1898 |
|
// Read the attribute type. |
1899 |
|
requireWhitespace(); |
1900 |
|
type = readAttType(); |
1901 |
|
|
1902 |
|
// Get the string of enumerated values if necessary. |
1903 |
|
if (handler.stringInterning) |
1904 |
|
{ |
1905 |
|
if ("ENUMERATION" == type || "NOTATION" == type) |
1906 |
|
{ |
1907 |
|
enumer = dataBufferToString(); |
1908 |
|
} |
1909 |
|
} |
1910 |
|
else |
1911 |
|
{ |
1912 |
|
if ("ENUMERATION".equals(type) || "NOTATION".equals(type)) |
1913 |
|
{ |
1914 |
|
enumer = dataBufferToString(); |
1915 |
|
} |
1916 |
|
} |
1917 |
|
|
1918 |
|
// Read the default value. |
1919 |
|
requireWhitespace(); |
1920 |
|
parseDefault(elementName, name, type, enumer); |
1921 |
|
} |
1922 |
|
|
1923 |
/** |
/** |
1924 |
* Parse the attribute type. |
* Parse the attribute type. |
1926 |
* [54] AttType ::= StringType | TokenizedType | EnumeratedType |
* [54] AttType ::= StringType | TokenizedType | EnumeratedType |
1927 |
* [55] StringType ::= 'CDATA' |
* [55] StringType ::= 'CDATA' |
1928 |
* [56] TokenizedType ::= 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' |
* [56] TokenizedType ::= 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' |
1929 |
* | 'ENTITIES' | 'NMTOKEN' | 'NMTOKENS' |
* | 'ENTITIES' | 'NMTOKEN' | 'NMTOKENS' |
1930 |
* [57] EnumeratedType ::= NotationType | Enumeration |
* [57] EnumeratedType ::= NotationType | Enumeration |
1931 |
* </pre> |
* </pre> |
1932 |
*/ |
*/ |
1933 |
private String readAttType () |
private String readAttType() |
1934 |
throws Exception |
throws Exception |
1935 |
{ |
{ |
1936 |
if (tryRead ('(')) { |
if (tryRead('(')) |
1937 |
parseEnumeration (false); |
{ |
1938 |
return "ENUMERATION"; |
parseEnumeration(false); |
1939 |
} else { |
return "ENUMERATION"; |
1940 |
String typeString = readNmtoken (true); |
} |
1941 |
if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { |
else |
1942 |
if ("NOTATION" == typeString) { |
{ |
1943 |
parseNotationType (); |
String typeString = readNmtoken(true); |
1944 |
return typeString; |
if (handler.stringInterning) |
1945 |
} else if ("CDATA" == typeString |
{ |
1946 |
|| "ID" == typeString |
if ("NOTATION" == typeString) |
1947 |
|| "IDREF" == typeString |
{ |
1948 |
|| "IDREFS" == typeString |
parseNotationType(); |
1949 |
|| "ENTITY" == typeString |
return typeString; |
1950 |
|| "ENTITIES" == typeString |
} |
1951 |
|| "NMTOKEN" == typeString |
else if ("CDATA" == typeString |
1952 |
|| "NMTOKENS" == typeString) |
|| "ID" == typeString |
1953 |
return typeString; |
|| "IDREF" == typeString |
1954 |
} else { |
|| "IDREFS" == typeString |
1955 |
if ("NOTATION".equals(typeString)) { |
|| "ENTITY" == typeString |
1956 |
parseNotationType (); |
|| "ENTITIES" == typeString |
1957 |
return typeString; |
|| "NMTOKEN" == typeString |
1958 |
} else if ("CDATA".equals(typeString) |
|| "NMTOKENS" == typeString) |
1959 |
|| "ID".equals(typeString) |
{ |
1960 |
|| "IDREF".equals(typeString) |
return typeString; |
1961 |
|| "IDREFS".equals(typeString) |
} |
1962 |
|| "ENTITY".equals(typeString) |
} |
1963 |
|| "ENTITIES".equals(typeString) |
else |
1964 |
|| "NMTOKEN".equals(typeString) |
{ |
1965 |
|| "NMTOKENS".equals(typeString)) |
if ("NOTATION".equals(typeString)) |
1966 |
return typeString; |
{ |
1967 |
|
parseNotationType(); |
1968 |
|
return typeString; |
1969 |
|
} |
1970 |
|
else if ("CDATA".equals(typeString) |
1971 |
|
|| "ID".equals(typeString) |
1972 |
|
|| "IDREF".equals(typeString) |
1973 |
|
|| "IDREFS".equals(typeString) |
1974 |
|
|| "ENTITY".equals(typeString) |
1975 |
|
|| "ENTITIES".equals(typeString) |
1976 |
|
|| "NMTOKEN".equals(typeString) |
1977 |
|
|| "NMTOKENS".equals(typeString)) |
1978 |
|
{ |
1979 |
|
return typeString; |
1980 |
|
} |
1981 |
|
} |
1982 |
|
error("illegal attribute type", typeString, null); |
1983 |
|
return null; |
1984 |
} |
} |
|
error ("illegal attribute type", typeString, null); |
|
|
return null; |
|
|
} |
|
1985 |
} |
} |
1986 |
|
|
1987 |
|
/** |
1988 |
/** |
* Parse an enumeration. |
1989 |
* Parse an enumeration. |
* <pre> |
1990 |
* <pre> |
* [59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')' |
1991 |
* [59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')' |
* </pre> |
1992 |
* </pre> |
* <p>NOTE: the '(' has already been read. |
1993 |
* <p>NOTE: the '(' has already been read. |
*/ |
1994 |
*/ |
private void parseEnumeration(boolean isNames) |
|
private void parseEnumeration (boolean isNames) |
|
1995 |
throws Exception |
throws Exception |
1996 |
{ |
{ |
1997 |
dataBufferAppend ('('); |
dataBufferAppend('('); |
|
|
|
|
// Read the first token. |
|
|
skipWhitespace (); |
|
|
dataBufferAppend (readNmtoken (isNames)); |
|
|
// Read the remaining tokens. |
|
|
skipWhitespace (); |
|
|
while (!tryRead (')')) { |
|
|
require ('|'); |
|
|
dataBufferAppend ('|'); |
|
|
skipWhitespace (); |
|
|
dataBufferAppend (readNmtoken (isNames)); |
|
|
skipWhitespace (); |
|
|
} |
|
|
dataBufferAppend (')'); |
|
|
} |
|
1998 |
|
|
1999 |
|
// Read the first token. |
2000 |
|
skipWhitespace(); |
2001 |
|
dataBufferAppend(readNmtoken(isNames)); |
2002 |
|
// Read the remaining tokens. |
2003 |
|
skipWhitespace(); |
2004 |
|
while (!tryRead(')')) |
2005 |
|
{ |
2006 |
|
require('|'); |
2007 |
|
dataBufferAppend('|'); |
2008 |
|
skipWhitespace(); |
2009 |
|
dataBufferAppend(readNmtoken (isNames)); |
2010 |
|
skipWhitespace(); |
2011 |
|
} |
2012 |
|
dataBufferAppend(')'); |
2013 |
|
} |
2014 |
|
|
2015 |
/** |
/** |
2016 |
* Parse a notation type for an attribute. |
* Parse a notation type for an attribute. |
2017 |
* <pre> |
* <pre> |
2018 |
* [58] NotationType ::= 'NOTATION' S '(' S? NameNtoks |
* [58] NotationType ::= 'NOTATION' S '(' S? NameNtoks |
2019 |
* (S? '|' S? name)* S? ')' |
* (S? '|' S? name)* S? ')' |
2020 |
* </pre> |
* </pre> |
2021 |
* <p>NOTE: the 'NOTATION' has already been read |
* <p>NOTE: the 'NOTATION' has already been read |
2022 |
*/ |
*/ |
2023 |
private void parseNotationType () |
private void parseNotationType() |
2024 |
throws Exception |
throws Exception |
2025 |
{ |
{ |
2026 |
requireWhitespace (); |
requireWhitespace(); |
2027 |
require ('('); |
require('('); |
2028 |
|
|
2029 |
parseEnumeration (true); |
parseEnumeration(true); |
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Parse the default value for an attribute. |
|
|
* <pre> |
|
|
* [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' |
|
|
* | (('#FIXED' S)? AttValue) |
|
|
* </pre> |
|
|
*/ |
|
|
private void parseDefault ( |
|
|
String elementName, |
|
|
String name, |
|
|
String type, |
|
|
String enumer |
|
|
) throws Exception |
|
|
{ |
|
|
int valueType = ATTRIBUTE_DEFAULT_SPECIFIED; |
|
|
String value = null; |
|
|
int flags = LIT_ATTRIBUTE; |
|
|
boolean saved = expandPE; |
|
|
String defaultType = null; |
|
|
|
|
|
// LIT_ATTRIBUTE forces '<' checks now (ASAP) and turns whitespace |
|
|
// chars to spaces (doesn't matter when that's done if it doesn't |
|
|
// interfere with char refs expanding to whitespace). |
|
|
|
|
|
if (!skippedPE) { |
|
|
flags |= LIT_ENTITY_REF; |
|
|
if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { |
|
|
if ("CDATA" != type) |
|
|
flags |= LIT_NORMALIZE; |
|
|
} else { |
|
|
if (!"CDATA".equals(type)) |
|
|
flags |= LIT_NORMALIZE; |
|
|
} |
|
|
} |
|
|
|
|
|
expandPE = false; |
|
|
if (tryRead ('#')) { |
|
|
if (tryRead ("FIXED")) { |
|
|
defaultType = "#FIXED"; |
|
|
valueType = ATTRIBUTE_DEFAULT_FIXED; |
|
|
requireWhitespace (); |
|
|
value = readLiteral (flags); |
|
|
} else if (tryRead ("REQUIRED")) { |
|
|
defaultType = "#REQUIRED"; |
|
|
valueType = ATTRIBUTE_DEFAULT_REQUIRED; |
|
|
} else if (tryRead ("IMPLIED")) { |
|
|
defaultType = "#IMPLIED"; |
|
|
valueType = ATTRIBUTE_DEFAULT_IMPLIED; |
|
|
} else { |
|
|
error ("illegal keyword for attribute default value"); |
|
|
} |
|
|
} else |
|
|
value = readLiteral (flags); |
|
|
expandPE = saved; |
|
|
setAttribute (elementName, name, type, enumer, value, valueType); |
|
|
if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { |
|
|
if ("ENUMERATION" == type) |
|
|
type = enumer; |
|
|
else if ("NOTATION" == type) |
|
|
type = "NOTATION " + enumer; |
|
|
} else { |
|
|
if ("ENUMERATION".equals(type)) |
|
|
type = enumer; |
|
|
else if ("NOTATION".equals(type)) |
|
|
type = "NOTATION " + enumer; |
|
2030 |
} |
} |
|
if (!skippedPE) handler.getDeclHandler () |
|
|
.attributeDecl (elementName, name, type, defaultType, value); |
|
|
} |
|
|
|
|
2031 |
|
|
2032 |
/** |
/** |
2033 |
* Parse a conditional section. |
* Parse the default value for an attribute. |
2034 |
* <pre> |
* <pre> |
2035 |
* [61] conditionalSect ::= includeSect || ignoreSect |
* [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' |
2036 |
* [62] includeSect ::= '<![' S? 'INCLUDE' S? '[' |
* | (('#FIXED' S)? AttValue) |
2037 |
* extSubsetDecl ']]>' |
* </pre> |
2038 |
* [63] ignoreSect ::= '<![' S? 'IGNORE' S? '[' |
*/ |
2039 |
* ignoreSectContents* ']]>' |
private void parseDefault(String elementName, String name, |
2040 |
* [64] ignoreSectContents ::= Ignore |
String type, String enumer) |
|
* ('<![' ignoreSectContents* ']]>' Ignore )* |
|
|
* [65] Ignore ::= Char* - (Char* ( '<![' | ']]>') Char* ) |
|
|
* </pre> |
|
|
* <p> NOTE: the '>![' has already been read. |
|
|
*/ |
|
|
private void parseConditionalSect (char saved []) |
|
2041 |
throws Exception |
throws Exception |
2042 |
{ |
{ |
2043 |
skipWhitespace (); |
int valueType = ATTRIBUTE_DEFAULT_SPECIFIED; |
2044 |
if (tryRead ("INCLUDE")) { |
String value = null; |
2045 |
skipWhitespace (); |
int flags = LIT_ATTRIBUTE; |
2046 |
require ('['); |
boolean saved = expandPE; |
2047 |
// VC: Proper Conditional Section/PE Nesting |
String defaultType = null; |
2048 |
if (readBuffer != saved) |
|
2049 |
handler.verror ("Illegal Conditional Section/PE nesting"); |
// LIT_ATTRIBUTE forces '<' checks now (ASAP) and turns whitespace |
2050 |
skipWhitespace (); |
// chars to spaces (doesn't matter when that's done if it doesn't |
2051 |
while (!tryRead ("]]>")) { |
// interfere with char refs expanding to whitespace). |
2052 |
parseMarkupdecl (); |
|
2053 |
skipWhitespace (); |
if (!skippedPE) |
2054 |
} |
{ |
2055 |
} else if (tryRead ("IGNORE")) { |
flags |= LIT_ENTITY_REF; |
2056 |
skipWhitespace (); |
if (handler.stringInterning) |
2057 |
require ('['); |
{ |
2058 |
// VC: Proper Conditional Section/PE Nesting |
if ("CDATA" != type) |
2059 |
if (readBuffer != saved) |
{ |
2060 |
handler.verror ("Illegal Conditional Section/PE nesting"); |
flags |= LIT_NORMALIZE; |
2061 |
int nesting = 1; |
} |
2062 |
char c; |
} |
2063 |
expandPE = false; |
else |
2064 |
for (int nest = 1; nest > 0;) { |
{ |
2065 |
c = readCh (); |
if (!"CDATA".equals(type)) |
2066 |
switch (c) { |
{ |
2067 |
case '<': |
flags |= LIT_NORMALIZE; |
2068 |
if (tryRead ("![")) { |
} |
2069 |
nest++; |
} |
2070 |
} |
} |
2071 |
case ']': |
|
2072 |
if (tryRead ("]>")) { |
expandPE = false; |
2073 |
nest--; |
if (tryRead('#')) |
2074 |
} |
{ |
2075 |
} |
if (tryRead("FIXED")) |
2076 |
} |
{ |
2077 |
expandPE = true; |
defaultType = "#FIXED"; |
2078 |
} else { |
valueType = ATTRIBUTE_DEFAULT_FIXED; |
2079 |
error ("conditional section must begin with INCLUDE or IGNORE"); |
requireWhitespace(); |
2080 |
} |
value = readLiteral(flags); |
2081 |
} |
} |
2082 |
|
else if (tryRead("REQUIRED")) |
2083 |
private void parseCharRef () |
{ |
2084 |
|
defaultType = "#REQUIRED"; |
2085 |
|
valueType = ATTRIBUTE_DEFAULT_REQUIRED; |
2086 |
|
} |
2087 |
|
else if (tryRead("IMPLIED")) |
2088 |
|
{ |
2089 |
|
defaultType = "#IMPLIED"; |
2090 |
|
valueType = ATTRIBUTE_DEFAULT_IMPLIED; |
2091 |
|
} |
2092 |
|
else |
2093 |
|
{ |
2094 |
|
error("illegal keyword for attribute default value"); |
2095 |
|
} |
2096 |
|
} |
2097 |
|
else |
2098 |
|
{ |
2099 |
|
value = readLiteral(flags); |
2100 |
|
} |
2101 |
|
expandPE = saved; |
2102 |
|
setAttribute(elementName, name, type, enumer, value, valueType); |
2103 |
|
if (handler.stringInterning) |
2104 |
|
{ |
2105 |
|
if ("ENUMERATION" == type) |
2106 |
|
{ |
2107 |
|
type = enumer; |
2108 |
|
} |
2109 |
|
else if ("NOTATION" == type) |
2110 |
|
{ |
2111 |
|
type = "NOTATION " + enumer; |
2112 |
|
} |
2113 |
|
} |
2114 |
|
else |
2115 |
|
{ |
2116 |
|
if ("ENUMERATION".equals(type)) |
2117 |
|
{ |
2118 |
|
type = enumer; |
2119 |
|
} |
2120 |
|
else if ("NOTATION".equals(type)) |
2121 |
|
{ |
2122 |
|
type = "NOTATION " + enumer; |
2123 |
|
} |
2124 |
|
} |
2125 |
|
if (!skippedPE) |
2126 |
|
{ |
2127 |
|
handler.getDeclHandler().attributeDecl(elementName, name, type, |
2128 |
|
defaultType, value); |
2129 |
|
} |
2130 |
|
} |
2131 |
|
|
2132 |
|
/** |
2133 |
|
* Parse a conditional section. |
2134 |
|
* <pre> |
2135 |
|
* [61] conditionalSect ::= includeSect || ignoreSect |
2136 |
|
* [62] includeSect ::= '<![' S? 'INCLUDE' S? '[' |
2137 |
|
* extSubsetDecl ']]>' |
2138 |
|
* [63] ignoreSect ::= '<![' S? 'IGNORE' S? '[' |
2139 |
|
* ignoreSectContents* ']]>' |
2140 |
|
* [64] ignoreSectContents ::= Ignore |
2141 |
|
* ('<![' ignoreSectContents* ']]>' Ignore )* |
2142 |
|
* [65] Ignore ::= Char* - (Char* ( '<![' | ']]>') Char* ) |
2143 |
|
* </pre> |
2144 |
|
* <p> NOTE: the '>![' has already been read. |
2145 |
|
*/ |
2146 |
|
private void parseConditionalSect(char[] saved) |
2147 |
|
throws Exception |
2148 |
|
{ |
2149 |
|
skipWhitespace(); |
2150 |
|
if (tryRead("INCLUDE")) |
2151 |
|
{ |
2152 |
|
skipWhitespace(); |
2153 |
|
require('['); |
2154 |
|
// VC: Proper Conditional Section/PE Nesting |
2155 |
|
if (readBuffer != saved) |
2156 |
|
{ |
2157 |
|
handler.verror("Illegal Conditional Section/PE nesting"); |
2158 |
|
} |
2159 |
|
skipWhitespace(); |
2160 |
|
while (!tryRead("]]>")) |
2161 |
|
{ |
2162 |
|
parseMarkupdecl(); |
2163 |
|
skipWhitespace(); |
2164 |
|
} |
2165 |
|
} |
2166 |
|
else if (tryRead("IGNORE")) |
2167 |
|
{ |
2168 |
|
skipWhitespace(); |
2169 |
|
require('['); |
2170 |
|
// VC: Proper Conditional Section/PE Nesting |
2171 |
|
if (readBuffer != saved) |
2172 |
|
{ |
2173 |
|
handler.verror("Illegal Conditional Section/PE nesting"); |
2174 |
|
} |
2175 |
|
int nesting = 1; |
2176 |
|
char c; |
2177 |
|
expandPE = false; |
2178 |
|
for (int nest = 1; nest > 0; ) |
2179 |
|
{ |
2180 |
|
c = readCh(); |
2181 |
|
switch (c) |
2182 |
|
{ |
2183 |
|
case '<': |
2184 |
|
if (tryRead("![")) |
2185 |
|
{ |
2186 |
|
nest++; |
2187 |
|
} |
2188 |
|
case ']': |
2189 |
|
if (tryRead("]>")) |
2190 |
|
{ |
2191 |
|
nest--; |
2192 |
|
} |
2193 |
|
} |
2194 |
|
} |
2195 |
|
expandPE = true; |
2196 |
|
} |
2197 |
|
else |
2198 |
|
{ |
2199 |
|
error("conditional section must begin with INCLUDE or IGNORE"); |
2200 |
|
} |
2201 |
|
} |
2202 |
|
|
2203 |
|
private void parseCharRef() |
2204 |
throws SAXException, IOException |
throws SAXException, IOException |
2205 |
{ |
{ |
2206 |
parseCharRef (true /* do flushDataBuffer by default */); |
parseCharRef(true /* do flushDataBuffer by default */); |
2207 |
} |
} |
2208 |
|
|
2209 |
/** |
/** |
2213 |
* </pre> |
* </pre> |
2214 |
* <p>NOTE: the '&#' has already been read. |
* <p>NOTE: the '&#' has already been read. |
2215 |
*/ |
*/ |
2216 |
private void tryReadCharRef () |
private void tryReadCharRef() |
2217 |
throws SAXException, IOException |
throws SAXException, IOException |
2218 |
{ |
{ |
2219 |
int value = 0; |
int value = 0; |
2220 |
char c; |
char c; |
2221 |
|
|
2222 |
if (tryRead ('x')) { |
if (tryRead('x')) |
2223 |
|
{ |
2224 |
loop1: |
loop1: |
2225 |
while (true) { |
while (true) |
2226 |
c = readCh (); |
{ |
2227 |
int n; |
c = readCh(); |
2228 |
switch (c) { |
if (c == ';') |
2229 |
case '0': case '1': case '2': case '3': case '4': |
{ |
2230 |
case '5': case '6': case '7': case '8': case '9': |
break loop1; |
2231 |
n = c - '0'; |
} |
2232 |
break; |
else |
2233 |
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': |
{ |
2234 |
n = (c - 'a') + 10; |
int n = Character.digit(c, 16); |
2235 |
break; |
if (n == -1) |
2236 |
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': |
{ |
2237 |
n = (c - 'A') + 10; |
error("illegal character in character reference", c, null); |
2238 |
break; |
break loop1; |
2239 |
case ';': |
} |
2240 |
break loop1; |
value *= 16; |
2241 |
default: |
value += n; |
2242 |
error ("illegal character in character reference", c, null); |
} |
2243 |
break loop1; |
} |
2244 |
} |
} |
2245 |
value *= 16; |
else |
2246 |
value += n; |
{ |
|
} |
|
|
} else { |
|
2247 |
loop2: |
loop2: |
2248 |
while (true) { |
while (true) |
2249 |
c = readCh (); |
{ |
2250 |
switch (c) { |
c = readCh(); |
2251 |
case '0': case '1': case '2': case '3': case '4': |
if (c == ';') |
2252 |
case '5': case '6': case '7': case '8': case '9': |
{ |
2253 |
value *= 10; |
break loop2; |
2254 |
value += c - '0'; |
} |
2255 |
break; |
else |
2256 |
case ';': |
{ |
2257 |
break loop2; |
int n = Character.digit(c, 10); |
2258 |
default: |
if (n == -1) |
2259 |
error ("illegal character in character reference", c, null); |
{ |
2260 |
break loop2; |
error("illegal character in character reference", c, null); |
2261 |
} |
break loop2; |
2262 |
} |
} |
2263 |
} |
value *= 10; |
2264 |
|
value += n; |
2265 |
// check for character refs being legal XML |
} |
2266 |
if ((value < 0x0020 |
} |
2267 |
&& ! (value == '\n' || value == '\t' || value == '\r')) |
} |
2268 |
|| (value >= 0xD800 && value <= 0xDFFF) |
|
2269 |
|| value == 0xFFFE || value == 0xFFFF |
// check for character refs being legal XML |
2270 |
|| value > 0x0010ffff) |
if ((value < 0x0020 |
2271 |
error ("illegal XML character reference U+" |
&& ! (value == '\n' || value == '\t' || value == '\r')) |
2272 |
+ Integer.toHexString (value)); |
|| (value >= 0xD800 && value <= 0xDFFF) |
2273 |
|
|| value == 0xFFFE || value == 0xFFFF |
2274 |
// Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz |
|| value > 0x0010ffff) |
2275 |
// (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz: |
{ |
2276 |
if (value > 0x0010ffff) { |
error("illegal XML character reference U+" |
2277 |
// too big for surrogate |
+ Integer.toHexString(value)); |
2278 |
error ("character reference " + value + " is too large for UTF-16", |
} |
2279 |
new Integer (value).toString (), null); |
|
2280 |
} |
// Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz |
2281 |
|
// (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz: |
2282 |
|
if (value > 0x0010ffff) |
2283 |
|
{ |
2284 |
|
// too big for surrogate |
2285 |
|
error("character reference " + value + " is too large for UTF-16", |
2286 |
|
new Integer(value).toString(), null); |
2287 |
|
} |
2288 |
|
|
2289 |
} |
} |
2290 |
|
|
2291 |
/** |
/** |
2292 |
* Read and interpret a character reference. |
* Read and interpret a character reference. |
2293 |
* <pre> |
* <pre> |
2294 |
* [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' |
* [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' |
2295 |
* </pre> |
* </pre> |
2296 |
* <p>NOTE: the '&#' has already been read. |
* <p>NOTE: the '&#' has already been read. |
2297 |
*/ |
*/ |
2298 |
private void parseCharRef (boolean doFlush) |
private void parseCharRef(boolean doFlush) |
2299 |
throws SAXException, IOException |
throws SAXException, IOException |
2300 |
{ |
{ |
2301 |
int value = 0; |
int value = 0; |
2302 |
char c; |
char c; |
2303 |
|
|
2304 |
if (tryRead ('x')) { |
if (tryRead('x')) |
2305 |
|
{ |
2306 |
loop1: |
loop1: |
2307 |
while (true) { |
while (true) |
2308 |
c = readCh (); |
{ |
2309 |
int n; |
c = readCh(); |
2310 |
switch (c) { |
if (c == ';') |
2311 |
case '0': case '1': case '2': case '3': case '4': |
{ |
2312 |
case '5': case '6': case '7': case '8': case '9': |
break loop1; |
2313 |
n = c - '0'; |
} |
2314 |
break; |
else |
2315 |
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': |
{ |
2316 |
n = (c - 'a') + 10; |
int n = Character.digit(c, 16); |
2317 |
break; |
if (n == -1) |
2318 |
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': |
{ |
2319 |
n = (c - 'A') + 10; |
error("illegal character in character reference", c, null); |
2320 |
break; |
break loop1; |
2321 |
case ';': |
} |
2322 |
break loop1; |
value *= 16; |
2323 |
default: |
value += n; |
2324 |
error ("illegal character in character reference", c, null); |
} |
2325 |
break loop1; |
} |
2326 |
} |
} |
2327 |
value *= 16; |
else |
2328 |
value += n; |
{ |
|
} |
|
|
} else { |
|
2329 |
loop2: |
loop2: |
2330 |
while (true) { |
while (true) |
2331 |
c = readCh (); |
{ |
2332 |
switch (c) { |
c = readCh(); |
2333 |
case '0': case '1': case '2': case '3': case '4': |
if (c == ';') |
2334 |
case '5': case '6': case '7': case '8': case '9': |
{ |
2335 |
value *= 10; |
break loop2; |
2336 |
value += c - '0'; |
} |
2337 |
break; |
else |
2338 |
case ';': |
{ |
2339 |
break loop2; |
int n = Character.digit(c, 10); |
2340 |
default: |
if (n == -1) |
2341 |
error ("illegal character in character reference", c, null); |
{ |
2342 |
break loop2; |
error("illegal character in character reference", c, null); |
2343 |
} |
break loop2; |
2344 |
} |
} |
2345 |
} |
value *= 10; |
2346 |
|
value += c - '0'; |
2347 |
// check for character refs being legal XML |
} |
2348 |
if ((value < 0x0020 |
} |
2349 |
&& ! (value == '\n' || value == '\t' || value == '\r')) |
} |
2350 |
|| (value >= 0xD800 && value <= 0xDFFF) |
|
2351 |
|| value == 0xFFFE || value == 0xFFFF |
// check for character refs being legal XML |
2352 |
|| value > 0x0010ffff) |
if ((value < 0x0020 |
2353 |
error ("illegal XML character reference U+" |
&& ! (value == '\n' || value == '\t' || value == '\r')) |
2354 |
+ Integer.toHexString (value)); |
|| (value >= 0xD800 && value <= 0xDFFF) |
2355 |
|
|| value == 0xFFFE || value == 0xFFFF |
2356 |
// Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz |
|| value > 0x0010ffff) |
2357 |
// (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz: |
{ |
2358 |
if (value <= 0x0000ffff) { |
error("illegal XML character reference U+" |
2359 |
// no surrogates needed |
+ Integer.toHexString(value)); |
2360 |
dataBufferAppend ((char) value); |
} |
2361 |
} else if (value <= 0x0010ffff) { |
|
2362 |
value -= 0x10000; |
// Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz |
2363 |
// > 16 bits, surrogate needed |
// (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz: |
2364 |
dataBufferAppend ((char) (0xd800 | (value >> 10))); |
if (value <= 0x0000ffff) |
2365 |
dataBufferAppend ((char) (0xdc00 | (value & 0x0003ff))); |
{ |
2366 |
} else { |
// no surrogates needed |
2367 |
// too big for surrogate |
dataBufferAppend((char) value); |
2368 |
error ("character reference " + value + " is too large for UTF-16", |
} |
2369 |
new Integer (value).toString (), null); |
else if (value <= 0x0010ffff) |
2370 |
} |
{ |
2371 |
if (doFlush) dataBufferFlush (); |
value -= 0x10000; |
2372 |
} |
// > 16 bits, surrogate needed |
2373 |
|
dataBufferAppend((char) (0xd800 | (value >> 10))); |
2374 |
|
dataBufferAppend((char) (0xdc00 | (value & 0x0003ff))); |
2375 |
/** |
} |
2376 |
* Parse and expand an entity reference. |
else |
2377 |
* <pre> |
{ |
2378 |
* [68] EntityRef ::= '&' Name ';' |
// too big for surrogate |
2379 |
* </pre> |
error("character reference " + value + " is too large for UTF-16", |
2380 |
* <p>NOTE: the '&' has already been read. |
new Integer(value).toString(), null); |
2381 |
* @param externalAllowed External entities are allowed here. |
} |
2382 |
*/ |
if (doFlush) |
2383 |
private void parseEntityRef (boolean externalAllowed) |
{ |
2384 |
|
dataBufferFlush(); |
2385 |
|
} |
2386 |
|
} |
2387 |
|
|
2388 |
|
/** |
2389 |
|
* Parse and expand an entity reference. |
2390 |
|
* <pre> |
2391 |
|
* [68] EntityRef ::= '&' Name ';' |
2392 |
|
* </pre> |
2393 |
|
* <p>NOTE: the '&' has already been read. |
2394 |
|
* @param externalAllowed External entities are allowed here. |
2395 |
|
*/ |
2396 |
|
private void parseEntityRef(boolean externalAllowed) |
2397 |
throws SAXException, IOException |
throws SAXException, IOException |
2398 |
{ |
{ |
2399 |
String name; |
String name; |
2400 |
|
|
2401 |
name = readNmtoken (true); |
name = readNmtoken(true); |
2402 |
require (';'); |
require(';'); |
2403 |
switch (getEntityType (name)) { |
switch (getEntityType(name)) |
2404 |
case ENTITY_UNDECLARED: |
{ |
2405 |
// NOTE: XML REC describes amazingly convoluted handling for |
case ENTITY_UNDECLARED: |
2406 |
// this case. Nothing as meaningful as being a WFness error |
// NOTE: XML REC describes amazingly convoluted handling for |
2407 |
// unless the processor might _legitimately_ not have seen a |
// this case. Nothing as meaningful as being a WFness error |
2408 |
// declaration ... which is what this implements. |
// unless the processor might _legitimately_ not have seen a |
2409 |
String message; |
// declaration ... which is what this implements. |
2410 |
|
String message; |
2411 |
message = "reference to undeclared general entity " + name; |
|
2412 |
if (skippedPE && !docIsStandalone) { |
message = "reference to undeclared general entity " + name; |
2413 |
handler.verror (message); |
if (skippedPE && !docIsStandalone) |
2414 |
// we don't know this entity, and it might be external... |
{ |
2415 |
if (externalAllowed) |
handler.verror(message); |
2416 |
handler.skippedEntity (name); |
// we don't know this entity, and it might be external... |
2417 |
} else |
if (externalAllowed) |
2418 |
error (message); |
{ |
2419 |
break; |
handler.skippedEntity(name); |
2420 |
case ENTITY_INTERNAL: |
} |
2421 |
pushString (name, getEntityValue (name)); |
} |
2422 |
|
else |
2423 |
//workaround for possible input pop before marking |
{ |
2424 |
//the buffer reading position |
error(message); |
2425 |
char t = readCh (); |
} |
2426 |
unread (t); |
break; |
2427 |
int bufferPosMark = readBufferPos; |
case ENTITY_INTERNAL: |
2428 |
|
pushString(name, getEntityValue(name)); |
2429 |
int end = readBufferPos + getEntityValue (name).length(); |
|
2430 |
for(int k = readBufferPos; k < end; k++){ |
//workaround for possible input pop before marking |
2431 |
t = readCh (); |
//the buffer reading position |
2432 |
if (t == '&'){ |
char t = readCh(); |
2433 |
t = readCh (); |
unread(t); |
2434 |
if (t == '#'){ |
int bufferPosMark = readBufferPos; |
2435 |
//try to match a character ref |
|
2436 |
tryReadCharRef (); |
int end = readBufferPos + getEntityValue(name).length(); |
2437 |
|
for (int k = readBufferPos; k < end; k++) |
2438 |
//everything has been read |
{ |
2439 |
if (readBufferPos >= end) |
t = readCh(); |
2440 |
break; |
if (t == '&') |
2441 |
k = readBufferPos; |
{ |
2442 |
continue; |
t = readCh(); |
2443 |
} |
if (t == '#') |
2444 |
else if (Character.isLetter(t)){ |
{ |
2445 |
//looks like an entity ref |
//try to match a character ref |
2446 |
unread (t); |
tryReadCharRef(); |
2447 |
readNmtoken (true); |
|
2448 |
require (';'); |
//everything has been read |
2449 |
|
if (readBufferPos >= end) |
2450 |
//everything has been read |
{ |
2451 |
if (readBufferPos >= end) |
break; |
2452 |
break; |
} |
2453 |
k = readBufferPos; |
k = readBufferPos; |
2454 |
continue; |
continue; |
2455 |
} |
} |
2456 |
error(" malformed entity reference"); |
else if (Character.isLetter(t)) |
2457 |
} |
{ |
2458 |
|
//looks like an entity ref |
2459 |
|
unread(t); |
2460 |
|
readNmtoken(true); |
2461 |
|
require(';'); |
2462 |
|
|
2463 |
|
//everything has been read |
2464 |
|
if (readBufferPos >= end) |
2465 |
|
{ |
2466 |
|
break; |
2467 |
|
} |
2468 |
|
k = readBufferPos; |
2469 |
|
continue; |
2470 |
|
} |
2471 |
|
error(" malformed entity reference"); |
2472 |
|
} |
2473 |
|
|
2474 |
} |
} |
2475 |
readBufferPos = bufferPosMark; |
readBufferPos = bufferPosMark; |
2476 |
break; |
break; |
2477 |
case ENTITY_TEXT: |
case ENTITY_TEXT: |
2478 |
if (externalAllowed) { |
if (externalAllowed) |
2479 |
pushURL (false, name, getEntityIds (name), |
{ |
2480 |
null, null, null, true); |
pushURL(false, name, getEntityIds(name), |
2481 |
} else { |
null, null, null, true); |
2482 |
error ("reference to external entity in attribute value.", |
} |
2483 |
name, null); |
else |
2484 |
} |
{ |
2485 |
break; |
error("reference to external entity in attribute value.", |
2486 |
case ENTITY_NDATA: |
name, null); |
2487 |
if (externalAllowed) { |
} |
2488 |
error ("unparsed entity reference in content", name, null); |
break; |
2489 |
} else { |
case ENTITY_NDATA: |
2490 |
error ("reference to external entity in attribute value.", |
if (externalAllowed) |
2491 |
name, null); |
{ |
2492 |
} |
error("unparsed entity reference in content", name, null); |
2493 |
break; |
} |
2494 |
default: |
else |
2495 |
throw new RuntimeException (); |
{ |
2496 |
} |
error("reference to external entity in attribute value.", |
2497 |
} |
name, null); |
2498 |
|
} |
2499 |
|
break; |
2500 |
/** |
default: |
2501 |
* Parse and expand a parameter entity reference. |
throw new RuntimeException(); |
2502 |
* <pre> |
} |
2503 |
* [69] PEReference ::= '%' Name ';' |
} |
2504 |
* </pre> |
|
2505 |
* <p>NOTE: the '%' has already been read. |
/** |
2506 |
*/ |
* Parse and expand a parameter entity reference. |
2507 |
private void parsePEReference () |
* <pre> |
2508 |
|
* [69] PEReference ::= '%' Name ';' |
2509 |
|
* </pre> |
2510 |
|
* <p>NOTE: the '%' has already been read. |
2511 |
|
*/ |
2512 |
|
private void parsePEReference() |
2513 |
throws SAXException, IOException |
throws SAXException, IOException |
2514 |
{ |
{ |
2515 |
String name; |
String name; |
2516 |
|
|
2517 |
name = "%" + readNmtoken (true); |
name = "%" + readNmtoken(true); |
2518 |
require (';'); |
require(';'); |
2519 |
switch (getEntityType (name)) { |
switch (getEntityType(name)) |
2520 |
case ENTITY_UNDECLARED: |
{ |
2521 |
// VC: Entity Declared |
case ENTITY_UNDECLARED: |
2522 |
handler.verror ("reference to undeclared parameter entity " + name); |
// VC: Entity Declared |
2523 |
|
handler.verror("reference to undeclared parameter entity " + name); |
2524 |
// we should disable handling of all subsequent declarations |
|
2525 |
// unless this is a standalone document (info discarded) |
// we should disable handling of all subsequent declarations |
2526 |
break; |
// unless this is a standalone document (info discarded) |
2527 |
case ENTITY_INTERNAL: |
break; |
2528 |
if (inLiteral) |
case ENTITY_INTERNAL: |
2529 |
pushString (name, getEntityValue (name)); |
if (inLiteral) |
2530 |
else |
{ |
2531 |
pushString (name, ' ' + getEntityValue (name) + ' '); |
pushString(name, getEntityValue(name)); |
2532 |
break; |
} |
2533 |
case ENTITY_TEXT: |
else |
2534 |
if (!inLiteral) |
{ |
2535 |
pushString (null, " "); |
pushString(name, ' ' + getEntityValue(name) + ' '); |
2536 |
pushURL (true, name, getEntityIds (name), null, null, null, true); |
} |
2537 |
if (!inLiteral) |
break; |
2538 |
pushString (null, " "); |
case ENTITY_TEXT: |
2539 |
break; |
if (!inLiteral) |
2540 |
} |
{ |
2541 |
} |
pushString(null, " "); |
2542 |
|
} |
2543 |
/** |
pushURL(true, name, getEntityIds(name), null, null, null, true); |
2544 |
* Parse an entity declaration. |
if (!inLiteral) |
2545 |
* <pre> |
{ |
2546 |
* [70] EntityDecl ::= GEDecl | PEDecl |
pushString(null, " "); |
2547 |
* [71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>' |
} |
2548 |
* [72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>' |
break; |
2549 |
* [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) |
} |
2550 |
* [74] PEDef ::= EntityValue | ExternalID |
} |
2551 |
* [75] ExternalID ::= 'SYSTEM' S SystemLiteral |
|
2552 |
* | 'PUBLIC' S PubidLiteral S SystemLiteral |
/** |
2553 |
* [76] NDataDecl ::= S 'NDATA' S Name |
* Parse an entity declaration. |
2554 |
* </pre> |
* <pre> |
2555 |
* <p>NOTE: the '<!ENTITY' has already been read. |
* [70] EntityDecl ::= GEDecl | PEDecl |
2556 |
*/ |
* [71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>' |
2557 |
private void parseEntityDecl () |
* [72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>' |
2558 |
|
* [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) |
2559 |
|
* [74] PEDef ::= EntityValue | ExternalID |
2560 |
|
* [75] ExternalID ::= 'SYSTEM' S SystemLiteral |
2561 |
|
* | 'PUBLIC' S PubidLiteral S SystemLiteral |
2562 |
|
* [76] NDataDecl ::= S 'NDATA' S Name |
2563 |
|
* </pre> |
2564 |
|
* <p>NOTE: the '<!ENTITY' has already been read. |
2565 |
|
*/ |
2566 |
|
private void parseEntityDecl() |
2567 |
throws Exception |
throws Exception |
2568 |
{ |
{ |
2569 |
boolean peFlag = false; |
boolean peFlag = false; |
2570 |
int flags = 0; |
int flags = 0; |
2571 |
|
|
2572 |
// Check for a parameter entity. |
// Check for a parameter entity. |
2573 |
expandPE = false; |
expandPE = false; |
2574 |
requireWhitespace (); |
requireWhitespace(); |
2575 |
if (tryRead ('%')) { |
if (tryRead('%')) |
2576 |
peFlag = true; |
{ |
2577 |
requireWhitespace (); |
peFlag = true; |
2578 |
} |
requireWhitespace(); |
2579 |
expandPE = true; |
} |
2580 |
|
expandPE = true; |
2581 |
// Read the entity name, and prepend |
|
2582 |
// '%' if necessary. |
// Read the entity name, and prepend |
2583 |
String name = readNmtoken (true); |
// '%' if necessary. |
2584 |
//NE08 |
String name = readNmtoken(true); |
2585 |
if (name.indexOf(':') >= 0) |
//NE08 |
2586 |
error ("Illegal character(':') in entity name ", name, null); |
if (name.indexOf(':') >= 0) |
2587 |
if (peFlag) { |
{ |
2588 |
name = "%" + name; |
error("Illegal character(':') in entity name ", name, null); |
2589 |
} |
} |
2590 |
|
if (peFlag) |
2591 |
// Read the entity value. |
{ |
2592 |
requireWhitespace (); |
name = "%" + name; |
2593 |
char c = readCh (); |
} |
|
unread (c); |
|
|
if (c == '"' || c == '\'') { |
|
|
// Internal entity ... replacement text has expanded refs |
|
|
// to characters and PEs, but not to general entities |
|
|
String value = readLiteral (flags); |
|
|
setInternalEntity (name, value); |
|
|
} else { |
|
|
// Read the external IDs |
|
|
String ids [] = readExternalIds (false, false); |
|
|
|
|
|
// Check for NDATA declaration. |
|
|
boolean white = tryWhitespace (); |
|
|
if (!peFlag && tryRead ("NDATA")) { |
|
|
if (!white) |
|
|
error ("whitespace required before NDATA"); |
|
|
requireWhitespace (); |
|
|
String notationName = readNmtoken (true); |
|
|
if (!skippedPE) { |
|
|
setExternalEntity (name, ENTITY_NDATA, ids, notationName); |
|
|
handler.unparsedEntityDecl (name, ids, notationName); |
|
|
} |
|
|
} else if (!skippedPE) { |
|
|
setExternalEntity (name, ENTITY_TEXT, ids, null); |
|
|
handler.getDeclHandler () |
|
|
.externalEntityDecl (name, ids [0], |
|
|
handler.resolveURIs () |
|
|
// FIXME: ASSUMES not skipped |
|
|
// "false" forces error on bad URI |
|
|
? handler.absolutize (ids [2], ids [1], false) |
|
|
: ids [1]); |
|
|
} |
|
|
} |
|
|
|
|
|
// Finish the declaration. |
|
|
skipWhitespace (); |
|
|
require ('>'); |
|
|
} |
|
2594 |
|
|
2595 |
|
// Read the entity value. |
2596 |
|
requireWhitespace(); |
2597 |
|
char c = readCh(); |
2598 |
|
unread (c); |
2599 |
|
if (c == '"' || c == '\'') |
2600 |
|
{ |
2601 |
|
// Internal entity ... replacement text has expanded refs |
2602 |
|
// to characters and PEs, but not to general entities |
2603 |
|
String value = readLiteral(flags); |
2604 |
|
setInternalEntity(name, value); |
2605 |
|
} |
2606 |
|
else |
2607 |
|
{ |
2608 |
|
// Read the external IDs |
2609 |
|
ExternalIdentifiers ids = readExternalIds(false, false); |
2610 |
|
|
2611 |
|
// Check for NDATA declaration. |
2612 |
|
boolean white = tryWhitespace(); |
2613 |
|
if (!peFlag && tryRead("NDATA")) |
2614 |
|
{ |
2615 |
|
if (!white) |
2616 |
|
{ |
2617 |
|
error("whitespace required before NDATA"); |
2618 |
|
} |
2619 |
|
requireWhitespace(); |
2620 |
|
String notationName = readNmtoken(true); |
2621 |
|
if (!skippedPE) |
2622 |
|
{ |
2623 |
|
setExternalEntity(name, ENTITY_NDATA, ids, notationName); |
2624 |
|
handler.unparsedEntityDecl(name, ids.publicId, ids.systemId, |
2625 |
|
ids.baseUri, notationName); |
2626 |
|
} |
2627 |
|
} |
2628 |
|
else if (!skippedPE) |
2629 |
|
{ |
2630 |
|
setExternalEntity(name, ENTITY_TEXT, ids, null); |
2631 |
|
handler.getDeclHandler() |
2632 |
|
.externalEntityDecl(name, ids.publicId, |
2633 |
|
handler.resolveURIs() |
2634 |
|
// FIXME: ASSUMES not skipped |
2635 |
|
// "false" forces error on bad URI |
2636 |
|
? handler.absolutize(ids.baseUri, |
2637 |
|
ids.systemId, |
2638 |
|
false) |
2639 |
|
: ids.systemId); |
2640 |
|
} |
2641 |
|
} |
2642 |
|
|
2643 |
|
// Finish the declaration. |
2644 |
|
skipWhitespace(); |
2645 |
|
require('>'); |
2646 |
|
} |
2647 |
|
|
2648 |
/** |
/** |
2649 |
* Parse a notation declaration. |
* Parse a notation declaration. |
2650 |
* <pre> |
* <pre> |
2651 |
* [82] NotationDecl ::= '<!NOTATION' S Name S |
* [82] NotationDecl ::= '<!NOTATION' S Name S |
2652 |
* (ExternalID | PublicID) S? '>' |
* (ExternalID | PublicID) S? '>' |
2653 |
* [83] PublicID ::= 'PUBLIC' S PubidLiteral |
* [83] PublicID ::= 'PUBLIC' S PubidLiteral |
2654 |
* </pre> |
* </pre> |
2655 |
* <P>NOTE: the '<!NOTATION' has already been read. |
* <P>NOTE: the '<!NOTATION' has already been read. |
2656 |
*/ |
*/ |
2657 |
private void parseNotationDecl () |
private void parseNotationDecl() |
2658 |
throws Exception |
throws Exception |
2659 |
{ |
{ |
2660 |
String nname, ids[]; |
String nname; |
2661 |
|
ExternalIdentifiers ids; |
|
|
|
|
requireWhitespace (); |
|
|
nname = readNmtoken (true); |
|
|
//NE08 |
|
|
if (nname.indexOf(':') >= 0) |
|
|
error ("Illegal character(':') in notation name ", nname, null); |
|
|
requireWhitespace (); |
|
|
|
|
|
// Read the external identifiers. |
|
|
ids = readExternalIds (true, false); |
|
|
|
|
|
// Register the notation. |
|
|
setNotation (nname, ids); |
|
2662 |
|
|
2663 |
skipWhitespace (); |
requireWhitespace(); |
2664 |
require ('>'); |
nname = readNmtoken(true); |
2665 |
} |
//NE08 |
2666 |
|
if (nname.indexOf(':') >= 0) |
2667 |
|
{ |
2668 |
|
error("Illegal character(':') in notation name ", nname, null); |
2669 |
|
} |
2670 |
|
requireWhitespace(); |
2671 |
|
|
2672 |
|
// Read the external identifiers. |
2673 |
|
ids = readExternalIds(true, false); |
2674 |
|
|
2675 |
/** |
// Register the notation. |
2676 |
* Parse character data. |
setNotation(nname, ids); |
2677 |
* <pre> |
|
2678 |
* [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) |
skipWhitespace(); |
2679 |
* </pre> |
require('>'); |
2680 |
*/ |
} |
2681 |
private void parseCharData () |
|
2682 |
|
/** |
2683 |
|
* Parse character data. |
2684 |
|
* <pre> |
2685 |
|
* [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) |
2686 |
|
* </pre> |
2687 |
|
*/ |
2688 |
|
private void parseCharData() |
2689 |
throws Exception |
throws Exception |
2690 |
{ |
{ |
2691 |
char c; |
char c; |
2692 |
int state = 0; |
int state = 0; |
2693 |
boolean pureWhite = false; |
boolean pureWhite = false; |
|
|
|
|
// assert (dataBufferPos == 0); |
|
|
|
|
|
// are we expecting pure whitespace? it might be dirty... |
|
|
if ((currentElementContent == CONTENT_ELEMENTS) && !isDirtyCurrentElement) |
|
|
pureWhite = true; |
|
|
|
|
|
// always report right out of readBuffer |
|
|
// to minimize (pointless) buffer copies |
|
|
while (true) { |
|
|
int lineAugment = 0; |
|
|
int columnAugment = 0; |
|
|
int i; |
|
|
|
|
|
loop: |
|
|
for (i = readBufferPos; i < readBufferLength; i++) { |
|
|
switch (c = readBuffer [i]) { |
|
|
case '\n': |
|
|
lineAugment++; |
|
|
columnAugment = 0; |
|
|
// pureWhite unmodified |
|
|
break; |
|
|
case '\r': // should not happen!! |
|
|
case '\t': |
|
|
case ' ': |
|
|
// pureWhite unmodified |
|
|
columnAugment++; |
|
|
break; |
|
|
case '&': |
|
|
case '<': |
|
|
columnAugment++; |
|
|
// pureWhite unmodified |
|
|
// CLEAN end of text sequence |
|
|
state = 1; |
|
|
break loop; |
|
|
case ']': |
|
|
// that's not a whitespace char, and |
|
|
// can not terminate pure whitespace either |
|
|
pureWhite = false; |
|
|
if ((i + 2) < readBufferLength) { |
|
|
if (readBuffer [i + 1] == ']' |
|
|
&& readBuffer [i + 2] == '>') { |
|
|
// ERROR end of text sequence |
|
|
state = 2; |
|
|
break loop; |
|
|
} |
|
|
} else { |
|
|
// FIXME missing two end-of-buffer cases |
|
|
} |
|
|
columnAugment++; |
|
|
break; |
|
|
default: |
|
|
if ((c < 0x0020 || c > 0xFFFD) |
|
|
|| ((c >= 0x007f) && (c <= 0x009f) && (c != 0x0085) |
|
|
&& xmlVersion == XML_11)) |
|
|
error ("illegal XML character U+" |
|
|
+ Integer.toHexString (c)); |
|
|
// that's not a whitespace char |
|
|
pureWhite = false; |
|
|
columnAugment++; |
|
|
} |
|
|
} |
|
|
|
|
|
// report text thus far |
|
|
if (lineAugment > 0) { |
|
|
line += lineAugment; |
|
|
column = columnAugment; |
|
|
} else { |
|
|
column += columnAugment; |
|
|
} |
|
|
|
|
|
// report characters/whitspace |
|
|
int length = i - readBufferPos; |
|
|
|
|
|
if (length != 0) { |
|
|
if (pureWhite) |
|
|
handler.ignorableWhitespace (readBuffer, |
|
|
readBufferPos, length); |
|
|
else |
|
|
handler.charData (readBuffer, readBufferPos, length); |
|
|
readBufferPos = i; |
|
|
} |
|
|
|
|
|
if (state != 0) |
|
|
break; |
|
|
|
|
|
// fill next buffer from this entity, or |
|
|
// pop stack and continue with previous entity |
|
|
unread (readCh ()); |
|
|
} |
|
|
if (!pureWhite) |
|
|
isDirtyCurrentElement = true; |
|
|
// finish, maybe with error |
|
|
if (state != 1) // finish, no error |
|
|
error ("character data may not contain ']]>'"); |
|
|
} |
|
|
|
|
2694 |
|
|
2695 |
////////////////////////////////////////////////////////////////////// |
// assert (dataBufferPos == 0); |
2696 |
// High-level reading and scanning methods. |
|
2697 |
////////////////////////////////////////////////////////////////////// |
// are we expecting pure whitespace? it might be dirty... |
2698 |
|
if ((currentElementContent == CONTENT_ELEMENTS) && !isDirtyCurrentElement) |
2699 |
|
{ |
2700 |
|
pureWhite = true; |
2701 |
|
} |
2702 |
|
|
2703 |
/** |
// always report right out of readBuffer |
2704 |
* Require whitespace characters. |
// to minimize (pointless) buffer copies |
2705 |
*/ |
while (true) |
2706 |
private void requireWhitespace () |
{ |
2707 |
|
int lineAugment = 0; |
2708 |
|
int columnAugment = 0; |
2709 |
|
int i; |
2710 |
|
|
2711 |
|
loop: |
2712 |
|
for (i = readBufferPos; i < readBufferLength; i++) |
2713 |
|
{ |
2714 |
|
switch (c = readBuffer[i]) |
2715 |
|
{ |
2716 |
|
case '\n': |
2717 |
|
lineAugment++; |
2718 |
|
columnAugment = 0; |
2719 |
|
// pureWhite unmodified |
2720 |
|
break; |
2721 |
|
case '\r': // should not happen!! |
2722 |
|
case '\t': |
2723 |
|
case ' ': |
2724 |
|
// pureWhite unmodified |
2725 |
|
columnAugment++; |
2726 |
|
break; |
2727 |
|
case '&': |
2728 |
|
case '<': |
2729 |
|
columnAugment++; |
2730 |
|
// pureWhite unmodified |
2731 |
|
// CLEAN end of text sequence |
2732 |
|
state = 1; |
2733 |
|
break loop; |
2734 |
|
case ']': |
2735 |
|
// that's not a whitespace char, and |
2736 |
|
// can not terminate pure whitespace either |
2737 |
|
pureWhite = false; |
2738 |
|
if ((i + 2) < readBufferLength) |
2739 |
|
{ |
2740 |
|
if (readBuffer [i + 1] == ']' |
2741 |
|
&& readBuffer [i + 2] == '>') |
2742 |
|
{ |
2743 |
|
// ERROR end of text sequence |
2744 |
|
state = 2; |
2745 |
|
break loop; |
2746 |
|
} |
2747 |
|
} |
2748 |
|
else |
2749 |
|
{ |
2750 |
|
// FIXME missing two end-of-buffer cases |
2751 |
|
} |
2752 |
|
columnAugment++; |
2753 |
|
break; |
2754 |
|
default: |
2755 |
|
if ((c < 0x0020 || c > 0xFFFD) |
2756 |
|
|| ((c >= 0x007f) && (c <= 0x009f) && (c != 0x0085) |
2757 |
|
&& xmlVersion == XML_11)) |
2758 |
|
{ |
2759 |
|
error("illegal XML character U+" |
2760 |
|
+ Integer.toHexString(c)); |
2761 |
|
} |
2762 |
|
// that's not a whitespace char |
2763 |
|
pureWhite = false; |
2764 |
|
columnAugment++; |
2765 |
|
} |
2766 |
|
} |
2767 |
|
|
2768 |
|
// report text thus far |
2769 |
|
if (lineAugment > 0) |
2770 |
|
{ |
2771 |
|
line += lineAugment; |
2772 |
|
column = columnAugment; |
2773 |
|
} |
2774 |
|
else |
2775 |
|
{ |
2776 |
|
column += columnAugment; |
2777 |
|
} |
2778 |
|
|
2779 |
|
// report characters/whitspace |
2780 |
|
int length = i - readBufferPos; |
2781 |
|
|
2782 |
|
if (length != 0) |
2783 |
|
{ |
2784 |
|
if (pureWhite) |
2785 |
|
{ |
2786 |
|
handler.ignorableWhitespace(readBuffer, |
2787 |
|
readBufferPos, length); |
2788 |
|
} |
2789 |
|
else |
2790 |
|
{ |
2791 |
|
handler.charData(readBuffer, readBufferPos, length); |
2792 |
|
} |
2793 |
|
readBufferPos = i; |
2794 |
|
} |
2795 |
|
|
2796 |
|
if (state != 0) |
2797 |
|
{ |
2798 |
|
break; |
2799 |
|
} |
2800 |
|
|
2801 |
|
// fill next buffer from this entity, or |
2802 |
|
// pop stack and continue with previous entity |
2803 |
|
unread(readCh()); |
2804 |
|
} |
2805 |
|
if (!pureWhite) |
2806 |
|
{ |
2807 |
|
isDirtyCurrentElement = true; |
2808 |
|
} |
2809 |
|
// finish, maybe with error |
2810 |
|
if (state != 1) // finish, no error |
2811 |
|
{ |
2812 |
|
error("character data may not contain ']]>'"); |
2813 |
|
} |
2814 |
|
} |
2815 |
|
|
2816 |
|
////////////////////////////////////////////////////////////////////// |
2817 |
|
// High-level reading and scanning methods. |
2818 |
|
////////////////////////////////////////////////////////////////////// |
2819 |
|
|
2820 |
|
/** |
2821 |
|
* Require whitespace characters. |
2822 |
|
*/ |
2823 |
|
private void requireWhitespace() |
2824 |
throws SAXException, IOException |
throws SAXException, IOException |
2825 |
{ |
{ |
2826 |
char c = readCh (); |
char c = readCh(); |
2827 |
if (isWhitespace (c)) { |
if (isWhitespace(c)) |
2828 |
skipWhitespace (); |
{ |
2829 |
} else { |
skipWhitespace(); |
2830 |
error ("whitespace required", c, null); |
} |
2831 |
} |
else |
2832 |
} |
{ |
2833 |
|
error("whitespace required", c, null); |
2834 |
|
} |
2835 |
|
} |
2836 |
|
|
2837 |
/** |
/** |
2838 |
* Skip whitespace characters. |
* Skip whitespace characters. |
2839 |
* <pre> |
* <pre> |
2840 |
* [3] S ::= (#x20 | #x9 | #xd | #xa)+ |
* [3] S ::= (#x20 | #x9 | #xd | #xa)+ |
2841 |
* </pre> |
* </pre> |
2842 |
*/ |
*/ |
2843 |
private void skipWhitespace () |
private void skipWhitespace() |
2844 |
throws SAXException, IOException |
throws SAXException, IOException |
2845 |
{ |
{ |
2846 |
// Start with a little cheat. Most of |
// Start with a little cheat. Most of |
2847 |
// the time, the white space will fall |
// the time, the white space will fall |
2848 |
// within the current read buffer; if |
// within the current read buffer; if |
2849 |
// not, then fall through. |
// not, then fall through. |
2850 |
if (USE_CHEATS) { |
if (USE_CHEATS) |
2851 |
int lineAugment = 0; |
{ |
2852 |
int columnAugment = 0; |
int lineAugment = 0; |
2853 |
|
int columnAugment = 0; |
2854 |
|
|
2855 |
loop: |
loop: |
2856 |
for (int i = readBufferPos; i < readBufferLength; i++) { |
for (int i = readBufferPos; i < readBufferLength; i++) |
2857 |
switch (readBuffer [i]) { |
{ |
2858 |
case ' ': |
switch (readBuffer[i]) |
2859 |
case '\t': |
{ |
2860 |
case '\r': |
case ' ': |
2861 |
columnAugment++; |
case '\t': |
2862 |
break; |
case '\r': |
2863 |
case '\n': |
columnAugment++; |
2864 |
lineAugment++; |
break; |
2865 |
columnAugment = 0; |
case '\n': |
2866 |
break; |
lineAugment++; |
2867 |
case '%': |
columnAugment = 0; |
2868 |
if (expandPE) |
break; |
2869 |
break loop; |
case '%': |
2870 |
// else fall through... |
if (expandPE) |
2871 |
default: |
{ |
2872 |
readBufferPos = i; |
break loop; |
2873 |
if (lineAugment > 0) { |
} |
2874 |
line += lineAugment; |
// else fall through... |
2875 |
column = columnAugment; |
default: |
2876 |
} else { |
readBufferPos = i; |
2877 |
column += columnAugment; |
if (lineAugment > 0) |
2878 |
} |
{ |
2879 |
return; |
line += lineAugment; |
2880 |
} |
column = columnAugment; |
2881 |
} |
} |
2882 |
} |
else |
2883 |
|
{ |
2884 |
// OK, do it the slow way. |
column += columnAugment; |
2885 |
char c = readCh (); |
} |
2886 |
while (isWhitespace (c)) { |
return; |
2887 |
c = readCh (); |
} |
2888 |
} |
} |
2889 |
unread (c); |
} |
2890 |
} |
|
2891 |
|
// OK, do it the slow way. |
2892 |
|
char c = readCh (); |
2893 |
/** |
while (isWhitespace(c)) |
2894 |
* Read a name or (when parsing an enumeration) name token. |
{ |
2895 |
* <pre> |
c = readCh(); |
2896 |
* [5] Name ::= (Letter | '_' | ':') (NameChar)* |
} |
2897 |
* [7] Nmtoken ::= (NameChar)+ |
unread(c); |
2898 |
* </pre> |
} |
2899 |
*/ |
|
2900 |
private String readNmtoken (boolean isName) |
/** |
2901 |
|
* Read a name or (when parsing an enumeration) name token. |
2902 |
|
* <pre> |
2903 |
|
* [5] Name ::= (Letter | '_' | ':') (NameChar)* |
2904 |
|
* [7] Nmtoken ::= (NameChar)+ |
2905 |
|
* </pre> |
2906 |
|
*/ |
2907 |
|
private String readNmtoken(boolean isName) |
2908 |
throws SAXException, IOException |
throws SAXException, IOException |
2909 |
{ |
{ |
2910 |
char c; |
char c; |
2911 |
|
|
2912 |
if (USE_CHEATS) { |
if (USE_CHEATS) |
2913 |
|
{ |
2914 |
loop: |
loop: |
2915 |
for (int i = readBufferPos; i < readBufferLength; i++) { |
for (int i = readBufferPos; i < readBufferLength; i++) |
2916 |
c = readBuffer [i]; |
{ |
2917 |
switch (c) { |
c = readBuffer[i]; |
2918 |
case '%': |
switch (c) |
2919 |
if (expandPE) |
{ |
2920 |
break loop; |
case '%': |
2921 |
// else fall through... |
if (expandPE) |
2922 |
|
{ |
2923 |
// What may legitimately come AFTER a name/nmtoken? |
break loop; |
2924 |
case '<': case '>': case '&': |
} |
2925 |
case ',': case '|': case '*': case '+': case '?': |
// else fall through... |
2926 |
case ')': |
|
2927 |
case '=': |
// What may legitimately come AFTER a name/nmtoken? |
2928 |
case '\'': case '"': |
case '<': case '>': case '&': |
2929 |
case '[': |
case ',': case '|': case '*': case '+': case '?': |
2930 |
case ' ': case '\t': case '\r': case '\n': |
case ')': |
2931 |
case ';': |
case '=': |
2932 |
case '/': |
case '\'': case '"': |
2933 |
int start = readBufferPos; |
case '[': |
2934 |
if (i == start) |
case ' ': case '\t': case '\r': case '\n': |
2935 |
error ("name expected", readBuffer [i], null); |
case ';': |
2936 |
readBufferPos = i; |
case '/': |
2937 |
return intern (readBuffer, start, i - start); |
int start = readBufferPos; |
2938 |
|
if (i == start) |
2939 |
default: |
{ |
2940 |
// FIXME ... per IBM's OASIS test submission, these: |
error("name expected", readBuffer[i], null); |
2941 |
// ? U+06dd |
} |
2942 |
// Combining U+309B |
readBufferPos = i; |
2943 |
//these switches are kind of ugly but at least we won't |
return intern(readBuffer, start, i - start); |
2944 |
//have to go over the whole lits for each char |
|
2945 |
if (isName && i == readBufferPos){ |
default: |
2946 |
char c2 = (char) (c & 0x00f0); |
// FIXME ... per IBM's OASIS test submission, these: |
2947 |
switch (c & 0xff00){ |
// ? U+06dd |
2948 |
//starting with 01 |
// Combining U+309B |
2949 |
case 0x0100: |
//these switches are kind of ugly but at least we won't |
2950 |
switch (c2){ |
//have to go over the whole lits for each char |
2951 |
case 0x0030: |
if (isName && i == readBufferPos) |
2952 |
if (c == 0x0132 || c == 0x0133 || c == 0x013f) |
{ |
2953 |
error ("Not a name start character, U+" |
char c2 = (char) (c & 0x00f0); |
2954 |
+ Integer.toHexString (c)); |
switch (c & 0xff00) |
2955 |
break; |
{ |
2956 |
case 0x0040: |
//starting with 01 |
2957 |
if (c == 0x0140 || c == 0x0149) |
case 0x0100: |
2958 |
error ("Not a name start character, U+" |
switch (c2) |
2959 |
+ Integer.toHexString (c)); |
{ |
2960 |
break; |
case 0x0030: |
2961 |
case 0x00c0: |
if (c == 0x0132 || c == 0x0133 || c == 0x013f) |
2962 |
if (c == 0x01c4 || c == 0x01cc) |
{ |
2963 |
error ("Not a name start character, U+" |
error("Not a name start character, U+" |
2964 |
+ Integer.toHexString (c)); |
+ Integer.toHexString(c)); |
2965 |
break; |
} |
2966 |
case 0x00f0: |
break; |
2967 |
if (c == 0x01f1 || c == 0x01f3) |
case 0x0040: |
2968 |
error ("Not a name start character, U+" |
if (c == 0x0140 || c == 0x0149) |
2969 |
+ Integer.toHexString (c)); |
{ |
2970 |
break; |
error("Not a name start character, U+" |
2971 |
case 0x00b0: |
+ Integer.toHexString(c)); |
2972 |
if (c == 0x01f1 || c == 0x01f3) |
} |
2973 |
error ("Not a name start character, U+" |
break; |
2974 |
+ Integer.toHexString (c)); |
case 0x00c0: |
2975 |
break; |
if (c == 0x01c4 || c == 0x01cc) |
2976 |
default: |
{ |
2977 |
if (c == 0x017f) |
error("Not a name start character, U+" |
2978 |
error ("Not a name start character, U+" |
+ Integer.toHexString(c)); |
2979 |
+ Integer.toHexString (c)); |
} |
2980 |
} |
break; |
2981 |
|
case 0x00f0: |
2982 |
break; |
if (c == 0x01f1 || c == 0x01f3) |
2983 |
//starting with 11 |
{ |
2984 |
case 0x1100: |
error("Not a name start character, U+" |
2985 |
switch (c2){ |
+ Integer.toHexString(c)); |
2986 |
case 0x0000: |
} |
2987 |
if (c == 0x1104 || c == 0x1108 || |
break; |
2988 |
c == 0x110a || c == 0x110d) |
case 0x00b0: |
2989 |
error ("Not a name start character, U+" |
if (c == 0x01f1 || c == 0x01f3) |
2990 |
+ Integer.toHexString (c)); |
{ |
2991 |
break; |
error("Not a name start character, U+" |
2992 |
case 0x0030: |
+ Integer.toHexString(c)); |
2993 |
if (c == 0x113b || c == 0x113f) |
} |
2994 |
error ("Not a name start character, U+" |
break; |
2995 |
+ Integer.toHexString (c)); |
default: |
2996 |
break; |
if (c == 0x017f) |
2997 |
case 0x0040: |
{ |
2998 |
if (c == 0x1141 || c == 0x114d |
error("Not a name start character, U+" |
2999 |
|| c == 0x114f ) |
+ Integer.toHexString(c)); |
3000 |
error ("Not a name start character, U+" |
} |
3001 |
+ Integer.toHexString (c)); |
} |
3002 |
break; |
|
3003 |
case 0x0050: |
break; |
3004 |
if (c == 0x1151 || c == 0x1156) |
//starting with 11 |
3005 |
error ("Not a name start character, U+" |
case 0x1100: |
3006 |
+ Integer.toHexString (c)); |
switch (c2) |
3007 |
break; |
{ |
3008 |
case 0x0060: |
case 0x0000: |
3009 |
if (c == 0x1162 || c == 0x1164 |
if (c == 0x1104 || c == 0x1108 || |
3010 |
|| c == 0x1166 || c == 0x116b |
c == 0x110a || c == 0x110d) |
3011 |
|| c == 0x116f) |
{ |
3012 |
error ("Not a name start character, U+" |
error("Not a name start character, U+" |
3013 |
+ Integer.toHexString (c)); |
+ Integer.toHexString(c)); |
3014 |
break; |
} |
3015 |
case 0x00b0: |
break; |
3016 |
if (c == 0x11b6 || c == 0x11b9 |
case 0x0030: |
3017 |
|| c == 0x11bb || c == 0x116f) |
if (c == 0x113b || c == 0x113f) |
3018 |
error ("Not a name start character, U+" |
{ |
3019 |
+ Integer.toHexString (c)); |
error("Not a name start character, U+" |
3020 |
break; |
+ Integer.toHexString(c)); |
3021 |
default: |
} |
3022 |
if (c == 0x1174 || c == 0x119f |
break; |
3023 |
|| c == 0x11ac || c == 0x11c3 |
case 0x0040: |
3024 |
|| c == 0x11f1) |
if (c == 0x1141 || c == 0x114d |
3025 |
error ("Not a name start character, U+" |
|| c == 0x114f ) |
3026 |
+ Integer.toHexString (c)); |
{ |
3027 |
} |
error("Not a name start character, U+" |
3028 |
break; |
+ Integer.toHexString(c)); |
3029 |
default: |
} |
3030 |
if (c == 0x0e46 || c == 0x1011 |
break; |
3031 |
|| c == 0x212f || c == 0x0587 |
case 0x0050: |
3032 |
|| c == 0x0230 ) |
if (c == 0x1151 || c == 0x1156) |
3033 |
error ("Not a name start character, U+" |
{ |
3034 |
+ Integer.toHexString (c)); |
error("Not a name start character, U+" |
3035 |
} |
+ Integer.toHexString(c)); |
3036 |
} |
} |
3037 |
// punt on exact tests from Appendix A; approximate |
break; |
3038 |
// them using the Unicode ID start/part rules |
case 0x0060: |
3039 |
if (i == readBufferPos && isName) { |
if (c == 0x1162 || c == 0x1164 |
3040 |
if (!Character.isUnicodeIdentifierStart (c) |
|| c == 0x1166 || c == 0x116b |
3041 |
&& c != ':' && c != '_') |
|| c == 0x116f) |
3042 |
error ("Not a name start character, U+" |
{ |
3043 |
+ Integer.toHexString (c)); |
error("Not a name start character, U+" |
3044 |
} else if (!Character.isUnicodeIdentifierPart (c) |
+ Integer.toHexString(c)); |
3045 |
&& c != '-' && c != ':' && c != '_' && c != '.' |
} |
3046 |
&& !isExtender (c)) |
break; |
3047 |
error ("Not a name character, U+" |
case 0x00b0: |
3048 |
+ Integer.toHexString (c)); |
if (c == 0x11b6 || c == 0x11b9 |
3049 |
} |
|| c == 0x11bb || c == 0x116f) |
3050 |
} |
{ |
3051 |
} |
error("Not a name start character, U+" |
3052 |
|
+ Integer.toHexString(c)); |
3053 |
nameBufferPos = 0; |
} |
3054 |
|
break; |
3055 |
|
default: |
3056 |
|
if (c == 0x1174 || c == 0x119f |
3057 |
|
|| c == 0x11ac || c == 0x11c3 |
3058 |
|
|| c == 0x11f1) |
3059 |
|
{ |
3060 |
|
error("Not a name start character, U+" |
3061 |
|
+ Integer.toHexString(c)); |
3062 |
|
} |
3063 |
|
} |
3064 |
|
break; |
3065 |
|
default: |
3066 |
|
if (c == 0x0e46 || c == 0x1011 |
3067 |
|
|| c == 0x212f || c == 0x0587 |
3068 |
|
|| c == 0x0230 ) |
3069 |
|
{ |
3070 |
|
error("Not a name start character, U+" |
3071 |
|
+ Integer.toHexString(c)); |
3072 |
|
} |
3073 |
|
} |
3074 |
|
} |
3075 |
|
// punt on exact tests from Appendix A; approximate |
3076 |
|
// them using the Unicode ID start/part rules |
3077 |
|
if (i == readBufferPos && isName) |
3078 |
|
{ |
3079 |
|
if (!Character.isUnicodeIdentifierStart(c) |
3080 |
|
&& c != ':' && c != '_') |
3081 |
|
{ |
3082 |
|
error("Not a name start character, U+" |
3083 |
|
+ Integer.toHexString(c)); |
3084 |
|
} |
3085 |
|
} |
3086 |
|
else if (!Character.isUnicodeIdentifierPart(c) |
3087 |
|
&& c != '-' && c != ':' && c != '_' && c != '.' |
3088 |
|
&& !isExtender(c)) |
3089 |
|
{ |
3090 |
|
error("Not a name character, U+" |
3091 |
|
+ Integer.toHexString(c)); |
3092 |
|
} |
3093 |
|
} |
3094 |
|
} |
3095 |
|
} |
3096 |
|
|
3097 |
|
nameBufferPos = 0; |
3098 |
|
|
3099 |
// Read the first character. |
// Read the first character. |
3100 |
loop: |
loop: |
3101 |
while (true) { |
while (true) |
3102 |
c = readCh (); |
{ |
3103 |
switch (c) { |
c = readCh(); |
3104 |
case '%': |
switch (c) |
3105 |
case '<': case '>': case '&': |
{ |
3106 |
case ',': case '|': case '*': case '+': case '?': |
case '%': |
3107 |
case ')': |
case '<': case '>': case '&': |
3108 |
case '=': |
case ',': case '|': case '*': case '+': case '?': |
3109 |
case '\'': case '"': |
case ')': |
3110 |
case '[': |
case '=': |
3111 |
case ' ': case '\t': case '\n': case '\r': |
case '\'': case '"': |
3112 |
case ';': |
case '[': |
3113 |
case '/': |
case ' ': case '\t': case '\n': case '\r': |
3114 |
unread (c); |
case ';': |
3115 |
if (nameBufferPos == 0) { |
case '/': |
3116 |
error ("name expected"); |
unread(c); |
3117 |
} |
if (nameBufferPos == 0) |
3118 |
// punt on exact tests from Appendix A, but approximate them |
{ |
3119 |
if (isName |
error ("name expected"); |
3120 |
&& !Character.isUnicodeIdentifierStart ( |
} |
3121 |
nameBuffer [0]) |
// punt on exact tests from Appendix A, but approximate them |
3122 |
&& ":_".indexOf (nameBuffer [0]) == -1) |
if (isName |
3123 |
error ("Not a name start character, U+" |
&& !Character.isUnicodeIdentifierStart(nameBuffer[0]) |
3124 |
+ Integer.toHexString (nameBuffer [0])); |
&& ":_".indexOf(nameBuffer[0]) == -1) |
3125 |
String s = intern (nameBuffer, 0, nameBufferPos); |
{ |
3126 |
nameBufferPos = 0; |
error("Not a name start character, U+" |
3127 |
return s; |
+ Integer.toHexString(nameBuffer[0])); |
3128 |
default: |
} |
3129 |
// punt on exact tests from Appendix A, but approximate them |
String s = intern(nameBuffer, 0, nameBufferPos); |
3130 |
|
nameBufferPos = 0; |
3131 |
if ((nameBufferPos != 0 || !isName) |
return s; |
3132 |
&& !Character.isUnicodeIdentifierPart (c) |
default: |
3133 |
&& ":-_.".indexOf (c) == -1 |
// punt on exact tests from Appendix A, but approximate them |
3134 |
&& !isExtender (c)) |
|
3135 |
error ("Not a name character, U+" |
if ((nameBufferPos != 0 || !isName) |
3136 |
+ Integer.toHexString (c)); |
&& !Character.isUnicodeIdentifierPart(c) |
3137 |
if (nameBufferPos >= nameBuffer.length) |
&& ":-_.".indexOf(c) == -1 |
3138 |
nameBuffer = |
&& !isExtender(c)) |
3139 |
(char[]) extendArray (nameBuffer, |
{ |
3140 |
nameBuffer.length, nameBufferPos); |
error("Not a name character, U+" |
3141 |
nameBuffer [nameBufferPos++] = c; |
+ Integer.toHexString(c)); |
3142 |
} |
} |
3143 |
} |
if (nameBufferPos >= nameBuffer.length) |
3144 |
} |
{ |
3145 |
|
nameBuffer = |
3146 |
private static boolean isExtender (char c) |
(char[]) extendArray(nameBuffer, |
3147 |
{ |
nameBuffer.length, nameBufferPos); |
3148 |
// [88] Extender ::= ... |
} |
3149 |
return c == 0x00b7 || c == 0x02d0 || c == 0x02d1 || c == 0x0387 |
nameBuffer[nameBufferPos++] = c; |
3150 |
|| c == 0x0640 || c == 0x0e46 || c == 0x0ec6 || c == 0x3005 |
} |
3151 |
|| (c >= 0x3031 && c <= 0x3035) |
} |
3152 |
|| (c >= 0x309d && c <= 0x309e) |
} |
3153 |
|| (c >= 0x30fc && c <= 0x30fe); |
|
3154 |
} |
private static boolean isExtender(char c) |
3155 |
|
{ |
3156 |
|
// [88] Extender ::= ... |
3157 |
|
return c == 0x00b7 || c == 0x02d0 || c == 0x02d1 || c == 0x0387 |
3158 |
|
|| c == 0x0640 || c == 0x0e46 || c == 0x0ec6 || c == 0x3005 |
3159 |
|
|| (c >= 0x3031 && c <= 0x3035) |
3160 |
|
|| (c >= 0x309d && c <= 0x309e) |
3161 |
|
|| (c >= 0x30fc && c <= 0x30fe); |
3162 |
|
} |
3163 |
|
|
3164 |
/** |
/** |
3165 |
* Read a literal. With matching single or double quotes as |
* Read a literal. With matching single or double quotes as |
3166 |
* delimiters (and not embedded!) this is used to parse: |
* delimiters (and not embedded!) this is used to parse: |
3167 |
* <pre> |
* <pre> |
3168 |
* [9] EntityValue ::= ... ([^%&] | PEReference | Reference)* ... |
* [9] EntityValue ::= ... ([^%&] | PEReference | Reference)* ... |
3169 |
* [10] AttValue ::= ... ([^<&] | Reference)* ... |
* [10] AttValue ::= ... ([^<&] | Reference)* ... |
3170 |
* [11] SystemLiteral ::= ... (URLchar - "'")* ... |
* [11] SystemLiteral ::= ... (URLchar - "'")* ... |
3171 |
* [12] PubidLiteral ::= ... (PubidChar - "'")* ... |
* [12] PubidLiteral ::= ... (PubidChar - "'")* ... |
3172 |
* </pre> |
* </pre> |
3173 |
* as well as the quoted strings in XML and text declarations |
* as well as the quoted strings in XML and text declarations |
3174 |
* (for version, encoding, and standalone) which have their |
* (for version, encoding, and standalone) which have their |
3175 |
* own constraints. |
* own constraints. |
3176 |
*/ |
*/ |
3177 |
private String readLiteral (int flags) |
private String readLiteral(int flags) |
3178 |
throws SAXException, IOException |
throws SAXException, IOException |
3179 |
{ |
{ |
3180 |
char delim, c; |
char delim, c; |
3181 |
int startLine = line; |
int startLine = line; |
3182 |
boolean saved = expandPE; |
boolean saved = expandPE; |
3183 |
boolean savedReport = doReport; |
boolean savedReport = doReport; |
3184 |
|
|
3185 |
// Find the first delimiter. |
// Find the first delimiter. |
3186 |
delim = readCh (); |
delim = readCh(); |
3187 |
if (delim != '"' && delim != '\'') { |
if (delim != '"' && delim != '\'') |
3188 |
error ("expected '\"' or \"'\"", delim, null); |
{ |
3189 |
return null; |
error("expected '\"' or \"'\"", delim, null); |
3190 |
} |
return null; |
3191 |
inLiteral = true; |
} |
3192 |
if ((flags & LIT_DISABLE_PE) != 0) |
inLiteral = true; |
3193 |
expandPE = false; |
if ((flags & LIT_DISABLE_PE) != 0) |
3194 |
doReport = false; |
{ |
3195 |
|
expandPE = false; |
3196 |
// Each level of input source has its own buffer; remember |
} |
3197 |
// ours, so we won't read the ending delimiter from any |
doReport = false; |
3198 |
// other input source, regardless of entity processing. |
|
3199 |
char ourBuf [] = readBuffer; |
// Each level of input source has its own buffer; remember |
3200 |
|
// ours, so we won't read the ending delimiter from any |
3201 |
// Read the literal. |
// other input source, regardless of entity processing. |
3202 |
try { |
char[] ourBuf = readBuffer; |
3203 |
c = readCh (); |
|
3204 |
boolean ampRead = false; |
// Read the literal. |
3205 |
|
try |
3206 |
|
{ |
3207 |
|
c = readCh(); |
3208 |
|
boolean ampRead = false; |
3209 |
loop: |
loop: |
3210 |
while (! (c == delim && readBuffer == ourBuf)) { |
while (! (c == delim && readBuffer == ourBuf)) |
3211 |
switch (c) { |
{ |
3212 |
// attributes and public ids are normalized |
switch (c) |
3213 |
// in almost the same ways |
{ |
3214 |
case '\n': |
// attributes and public ids are normalized |
3215 |
case '\r': |
// in almost the same ways |
3216 |
if ((flags & (LIT_ATTRIBUTE | LIT_PUBID)) != 0) |
case '\n': |
3217 |
c = ' '; |
case '\r': |
3218 |
break; |
if ((flags & (LIT_ATTRIBUTE | LIT_PUBID)) != 0) |
3219 |
case '\t': |
{ |
3220 |
if ((flags & LIT_ATTRIBUTE) != 0) |
c = ' '; |
3221 |
c = ' '; |
} |
3222 |
break; |
break; |
3223 |
case '&': |
case '\t': |
3224 |
c = readCh (); |
if ((flags & LIT_ATTRIBUTE) != 0) |
3225 |
// Char refs are expanded immediately, except for |
{ |
3226 |
// all the cases where it's deferred. |
c = ' '; |
3227 |
if (c == '#') { |
} |
3228 |
if ((flags & LIT_DISABLE_CREF) != 0) { |
break; |
3229 |
dataBufferAppend ('&'); |
case '&': |
3230 |
break; |
c = readCh(); |
3231 |
} |
// Char refs are expanded immediately, except for |
3232 |
parseCharRef (false /* Do not do flushDataBuffer */); |
// all the cases where it's deferred. |
3233 |
|
if (c == '#') |
3234 |
// exotic WFness risk: this is an entity literal, |
{ |
3235 |
// dataBuffer [dataBufferPos - 1] == '&', and |
if ((flags & LIT_DISABLE_CREF) != 0) |
3236 |
// following chars are a _partial_ entity/char ref |
{ |
3237 |
|
dataBufferAppend('&'); |
3238 |
// It looks like an entity ref ... |
break; |
3239 |
} else { |
} |
3240 |
unread (c); |
parseCharRef(false /* Do not do flushDataBuffer */); |
3241 |
// Expand it? |
|
3242 |
if ((flags & LIT_ENTITY_REF) > 0) { |
// exotic WFness risk: this is an entity literal, |
3243 |
parseEntityRef (false); |
// dataBuffer [dataBufferPos - 1] == '&', and |
3244 |
if (String.valueOf (readBuffer).equals("&")) |
// following chars are a _partial_ entity/char ref |
3245 |
ampRead = true; |
|
3246 |
|
// It looks like an entity ref ... |
3247 |
|
} |
3248 |
|
else |
3249 |
|
{ |
3250 |
|
unread(c); |
3251 |
|
// Expand it? |
3252 |
|
if ((flags & LIT_ENTITY_REF) > 0) |
3253 |
|
{ |
3254 |
|
parseEntityRef(false); |
3255 |
|
if (String.valueOf(readBuffer).equals("&")) |
3256 |
|
{ |
3257 |
|
ampRead = true; |
3258 |
|
} |
3259 |
//Is it just data? |
//Is it just data? |
3260 |
} else if ((flags & LIT_DISABLE_EREF) != 0) { |
} |
3261 |
dataBufferAppend ('&'); |
else if ((flags & LIT_DISABLE_EREF) != 0) |
3262 |
|
{ |
3263 |
// OK, it will be an entity ref -- expanded later. |
dataBufferAppend('&'); |
3264 |
} else { |
|
3265 |
String name = readNmtoken (true); |
// OK, it will be an entity ref -- expanded later. |
3266 |
require (';'); |
} |
3267 |
dataBufferAppend ('&'); |
else |
3268 |
dataBufferAppend (name); |
{ |
3269 |
dataBufferAppend (';'); |
String name = readNmtoken(true); |
3270 |
} |
require(';'); |
3271 |
} |
dataBufferAppend('&'); |
3272 |
c = readCh (); |
dataBufferAppend(name); |
3273 |
continue loop; |
dataBufferAppend(';'); |
3274 |
|
} |
3275 |
case '<': |
} |
3276 |
// and why? Perhaps so "&foo;" expands the same |
c = readCh(); |
3277 |
// inside and outside an attribute? |
continue loop; |
3278 |
if ((flags & LIT_ATTRIBUTE) != 0) |
|
3279 |
error ("attribute values may not contain '<'"); |
case '<': |
3280 |
break; |
// and why? Perhaps so "&foo;" expands the same |
3281 |
|
// inside and outside an attribute? |
3282 |
// We don't worry about case '%' and PE refs, readCh does. |
if ((flags & LIT_ATTRIBUTE) != 0) |
3283 |
|
{ |
3284 |
default: |
error("attribute values may not contain '<'"); |
3285 |
break; |
} |
3286 |
} |
break; |
3287 |
dataBufferAppend (c); |
|
3288 |
c = readCh (); |
// We don't worry about case '%' and PE refs, readCh does. |
3289 |
} |
|
3290 |
} catch (EOFException e) { |
default: |
3291 |
error ("end of input while looking for delimiter (started on line " |
break; |
3292 |
+ startLine + ')', null, new Character (delim).toString ()); |
} |
3293 |
} |
dataBufferAppend(c); |
3294 |
inLiteral = false; |
c = readCh(); |
3295 |
expandPE = saved; |
} |
3296 |
doReport = savedReport; |
} |
3297 |
|
catch (EOFException e) |
3298 |
// Normalise whitespace if necessary. |
{ |
3299 |
if ((flags & LIT_NORMALIZE) > 0) { |
error("end of input while looking for delimiter (started on line " |
3300 |
dataBufferNormalize (); |
+ startLine + ')', null, new Character(delim).toString()); |
3301 |
} |
} |
3302 |
|
inLiteral = false; |
3303 |
// Return the value. |
expandPE = saved; |
3304 |
return dataBufferToString (); |
doReport = savedReport; |
3305 |
} |
|
3306 |
|
// Normalise whitespace if necessary. |
3307 |
|
if ((flags & LIT_NORMALIZE) > 0) |
3308 |
/** |
{ |
3309 |
* Try reading external identifiers. |
dataBufferNormalize(); |
3310 |
* A system identifier is not required for notations. |
} |
3311 |
* @param inNotation Are we parsing a notation decl? |
|
3312 |
* @param isSubset Parsing external subset decl (may be omitted)? |
// Return the value. |
3313 |
* @return A three-member String array containing the identifiers, |
return dataBufferToString(); |
3314 |
* or nulls. Order: public, system, baseURI. |
} |
3315 |
*/ |
|
3316 |
private String[] readExternalIds (boolean inNotation, boolean isSubset) |
/** |
3317 |
|
* Try reading external identifiers. |
3318 |
|
* A system identifier is not required for notations. |
3319 |
|
* @param inNotation Are we parsing a notation decl? |
3320 |
|
* @param isSubset Parsing external subset decl (may be omitted)? |
3321 |
|
* @return A three-member String array containing the identifiers, |
3322 |
|
* or nulls. Order: public, system, baseURI. |
3323 |
|
*/ |
3324 |
|
private ExternalIdentifiers readExternalIds(boolean inNotation, |
3325 |
|
boolean isSubset) |
3326 |
throws Exception |
throws Exception |
3327 |
{ |
{ |
3328 |
char c; |
char c; |
3329 |
String ids[] = new String [3]; |
ExternalIdentifiers ids = new ExternalIdentifiers(); |
3330 |
int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; |
int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; |
3331 |
|
|
3332 |
if (tryRead ("PUBLIC")) { |
if (tryRead("PUBLIC")) |
3333 |
requireWhitespace (); |
{ |
3334 |
ids [0] = readLiteral (LIT_NORMALIZE | LIT_PUBID | flags); |
requireWhitespace(); |
3335 |
if (inNotation) { |
ids.publicId = readLiteral(LIT_NORMALIZE | LIT_PUBID | flags); |
3336 |
skipWhitespace (); |
if (inNotation) |
3337 |
c = readCh (); |
{ |
3338 |
unread (c); |
skipWhitespace(); |
3339 |
if (c == '"' || c == '\'') { |
c = readCh(); |
3340 |
ids [1] = readLiteral (flags); |
unread(c); |
3341 |
} |
if (c == '"' || c == '\'') |
3342 |
} else { |
{ |
3343 |
requireWhitespace (); |
ids.systemId = readLiteral(flags); |
3344 |
ids [1] = readLiteral (flags); |
} |
3345 |
} |
} |
3346 |
|
else |
3347 |
for (int i = 0; i < ids [0].length (); i++) { |
{ |
3348 |
c = ids [0].charAt (i); |
requireWhitespace(); |
3349 |
if (c >= 'a' && c <= 'z') |
ids.systemId = readLiteral(flags); |
3350 |
continue; |
} |
3351 |
if (c >= 'A' && c <= 'Z') |
|
3352 |
continue; |
for (int i = 0; i < ids.publicId.length(); i++) |
3353 |
if (" \r\n0123456789-' ()+,./:=?;!*#@$_%".indexOf (c) != -1) |
{ |
3354 |
continue; |
c = ids.publicId.charAt(i); |
3355 |
error ("illegal PUBLIC id character U+" |
if (c >= 'a' && c <= 'z') |
3356 |
+ Integer.toHexString (c)); |
{ |
3357 |
} |
continue; |
3358 |
} else if (tryRead ("SYSTEM")) { |
} |
3359 |
requireWhitespace (); |
if (c >= 'A' && c <= 'Z') |
3360 |
ids [1] = readLiteral (flags); |
{ |
3361 |
} else if (!isSubset) |
continue; |
3362 |
error ("missing SYSTEM or PUBLIC keyword"); |
} |
3363 |
|
if (" \r\n0123456789-' ()+,./:=?;!*#@$_%".indexOf(c) != -1) |
3364 |
if (ids [1] != null) { |
{ |
3365 |
if (ids [1].indexOf ('#') != -1) |
continue; |
3366 |
handler.verror ("SYSTEM id has a URI fragment: " + ids [1]); |
} |
3367 |
ids [2] = handler.getSystemId (); |
error("illegal PUBLIC id character U+" |
3368 |
if (ids [2] == null) |
+ Integer.toHexString(c)); |
3369 |
handler.warn ("No base URI; hope URI is absolute: " |
} |
3370 |
+ ids [1]); |
} |
3371 |
} |
else if (tryRead("SYSTEM")) |
3372 |
|
{ |
3373 |
return ids; |
requireWhitespace(); |
3374 |
} |
ids.systemId = readLiteral(flags); |
3375 |
|
} |
3376 |
|
else if (!isSubset) |
3377 |
/** |
{ |
3378 |
* Test if a character is whitespace. |
error("missing SYSTEM or PUBLIC keyword"); |
3379 |
* <pre> |
} |
3380 |
* [3] S ::= (#x20 | #x9 | #xd | #xa)+ |
|
3381 |
* </pre> |
if (ids.systemId != null) |
3382 |
* @param c The character to test. |
{ |
3383 |
* @return true if the character is whitespace. |
if (ids.systemId.indexOf('#') != -1) |
3384 |
*/ |
{ |
3385 |
private final boolean isWhitespace (char c) |
handler.verror("SYSTEM id has a URI fragment: " + ids.systemId); |
3386 |
{ |
} |
3387 |
if (c > 0x20) |
ids.baseUri = handler.getSystemId(); |
3388 |
return false; |
if (ids.baseUri == null && uriWarnings) |
3389 |
if (c == 0x20 || c == 0x0a || c == 0x09 || c == 0x0d) |
{ |
3390 |
return true; |
handler.warn("No base URI; hope URI is absolute: " |
3391 |
return false; // illegal ... |
+ ids.systemId); |
3392 |
} |
} |
3393 |
|
} |
3394 |
|
|
3395 |
////////////////////////////////////////////////////////////////////// |
return ids; |
3396 |
// Utility routines. |
} |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
/** |
|
|
* Add a character to the data buffer. |
|
|
*/ |
|
|
private void dataBufferAppend (char c) |
|
|
{ |
|
|
// Expand buffer if necessary. |
|
|
if (dataBufferPos >= dataBuffer.length) |
|
|
dataBuffer = |
|
|
(char[]) extendArray (dataBuffer, |
|
|
dataBuffer.length, dataBufferPos); |
|
|
dataBuffer [dataBufferPos++] = c; |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Add a string to the data buffer. |
|
|
*/ |
|
|
private void dataBufferAppend (String s) |
|
|
{ |
|
|
dataBufferAppend (s.toCharArray (), 0, s.length ()); |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Append (part of) a character array to the data buffer. |
|
|
*/ |
|
|
private void dataBufferAppend (char ch[], int start, int length) |
|
|
{ |
|
|
dataBuffer = (char[]) |
|
|
extendArray (dataBuffer, dataBuffer.length, |
|
|
dataBufferPos + length); |
|
3397 |
|
|
3398 |
System.arraycopy (ch, start, dataBuffer, dataBufferPos, length); |
/** |
3399 |
dataBufferPos += length; |
* Test if a character is whitespace. |
3400 |
} |
* <pre> |
3401 |
|
* [3] S ::= (#x20 | #x9 | #xd | #xa)+ |
3402 |
|
* </pre> |
3403 |
|
* @param c The character to test. |
3404 |
|
* @return true if the character is whitespace. |
3405 |
|
*/ |
3406 |
|
private final boolean isWhitespace(char c) |
3407 |
|
{ |
3408 |
|
if (c > 0x20) |
3409 |
|
{ |
3410 |
|
return false; |
3411 |
|
} |
3412 |
|
if (c == 0x20 || c == 0x0a || c == 0x09 || c == 0x0d) |
3413 |
|
{ |
3414 |
|
return true; |
3415 |
|
} |
3416 |
|
return false; // illegal ... |
3417 |
|
} |
3418 |
|
|
3419 |
|
////////////////////////////////////////////////////////////////////// |
3420 |
|
// Utility routines. |
3421 |
|
////////////////////////////////////////////////////////////////////// |
3422 |
|
|
3423 |
|
/** |
3424 |
|
* Add a character to the data buffer. |
3425 |
|
*/ |
3426 |
|
private void dataBufferAppend(char c) |
3427 |
|
{ |
3428 |
|
// Expand buffer if necessary. |
3429 |
|
if (dataBufferPos >= dataBuffer.length) |
3430 |
|
{ |
3431 |
|
dataBuffer = (char[]) extendArray(dataBuffer, |
3432 |
|
dataBuffer.length, dataBufferPos); |
3433 |
|
} |
3434 |
|
dataBuffer[dataBufferPos++] = c; |
3435 |
|
} |
3436 |
|
|
3437 |
/** |
/** |
3438 |
* Normalise space characters in the data buffer. |
* Add a string to the data buffer. |
3439 |
*/ |
*/ |
3440 |
private void dataBufferNormalize () |
private void dataBufferAppend(String s) |
3441 |
{ |
{ |
3442 |
int i = 0; |
dataBufferAppend(s.toCharArray(), 0, s.length()); |
3443 |
int j = 0; |
} |
|
int end = dataBufferPos; |
|
|
|
|
|
// Skip spaces at the start. |
|
|
while (j < end && dataBuffer [j] == ' ') { |
|
|
j++; |
|
|
} |
|
|
|
|
|
// Skip whitespace at the end. |
|
|
while (end > j && dataBuffer [end - 1] == ' ') { |
|
|
end --; |
|
|
} |
|
|
|
|
|
// Start copying to the left. |
|
|
while (j < end) { |
|
|
|
|
|
char c = dataBuffer [j++]; |
|
|
|
|
|
// Normalise all other spaces to |
|
|
// a single space. |
|
|
if (c == ' ') { |
|
|
while (j < end && dataBuffer [j++] == ' ') |
|
|
continue; |
|
|
dataBuffer [i++] = ' '; |
|
|
dataBuffer [i++] = dataBuffer [j - 1]; |
|
|
} else { |
|
|
dataBuffer [i++] = c; |
|
|
} |
|
|
} |
|
3444 |
|
|
3445 |
// The new length is <= the old one. |
/** |
3446 |
dataBufferPos = i; |
* Append (part of) a character array to the data buffer. |
3447 |
} |
*/ |
3448 |
|
private void dataBufferAppend(char[] ch, int start, int length) |
3449 |
|
{ |
3450 |
|
dataBuffer = (char[]) extendArray(dataBuffer, dataBuffer.length, |
3451 |
|
dataBufferPos + length); |
3452 |
|
|
3453 |
|
System.arraycopy(ch, start, dataBuffer, dataBufferPos, length); |
3454 |
|
dataBufferPos += length; |
3455 |
|
} |
3456 |
|
|
3457 |
|
/** |
3458 |
|
* Normalise space characters in the data buffer. |
3459 |
|
*/ |
3460 |
|
private void dataBufferNormalize() |
3461 |
|
{ |
3462 |
|
int i = 0; |
3463 |
|
int j = 0; |
3464 |
|
int end = dataBufferPos; |
3465 |
|
|
3466 |
|
// Skip spaces at the start. |
3467 |
|
while (j < end && dataBuffer[j] == ' ') |
3468 |
|
{ |
3469 |
|
j++; |
3470 |
|
} |
3471 |
|
|
3472 |
|
// Skip whitespace at the end. |
3473 |
|
while (end > j && dataBuffer[end - 1] == ' ') |
3474 |
|
{ |
3475 |
|
end --; |
3476 |
|
} |
3477 |
|
|
3478 |
/** |
// Start copying to the left. |
3479 |
* Convert the data buffer to a string. |
while (j < end) |
3480 |
*/ |
{ |
3481 |
private String dataBufferToString () |
|
3482 |
{ |
char c = dataBuffer[j++]; |
3483 |
String s = new String (dataBuffer, 0, dataBufferPos); |
|
3484 |
dataBufferPos = 0; |
// Normalise all other spaces to |
3485 |
return s; |
// a single space. |
3486 |
} |
if (c == ' ') |
3487 |
|
{ |
3488 |
|
while (j < end && dataBuffer[j++] == ' ') |
3489 |
|
{ |
3490 |
|
continue; |
3491 |
|
} |
3492 |
|
dataBuffer[i++] = ' '; |
3493 |
|
dataBuffer[i++] = dataBuffer[j - 1]; |
3494 |
|
} |
3495 |
|
else |
3496 |
|
{ |
3497 |
|
dataBuffer[i++] = c; |
3498 |
|
} |
3499 |
|
} |
3500 |
|
|
3501 |
|
// The new length is <= the old one. |
3502 |
|
dataBufferPos = i; |
3503 |
|
} |
3504 |
|
|
3505 |
|
/** |
3506 |
|
* Convert the data buffer to a string. |
3507 |
|
*/ |
3508 |
|
private String dataBufferToString() |
3509 |
|
{ |
3510 |
|
String s = new String(dataBuffer, 0, dataBufferPos); |
3511 |
|
dataBufferPos = 0; |
3512 |
|
return s; |
3513 |
|
} |
3514 |
|
|
3515 |
/** |
/** |
3516 |
* Flush the contents of the data buffer to the handler, as |
* Flush the contents of the data buffer to the handler, as |
3517 |
* appropriate, and reset the buffer for new input. |
* appropriate, and reset the buffer for new input. |
3518 |
*/ |
*/ |
3519 |
private void dataBufferFlush () |
private void dataBufferFlush() |
3520 |
throws SAXException |
throws SAXException |
3521 |
{ |
{ |
3522 |
if (currentElementContent == CONTENT_ELEMENTS |
if (currentElementContent == CONTENT_ELEMENTS |
3523 |
&& dataBufferPos > 0 |
&& dataBufferPos > 0 |
3524 |
&& !inCDATA |
&& !inCDATA) |
3525 |
) { |
{ |
3526 |
// We can't just trust the buffer to be whitespace, there |
// We can't just trust the buffer to be whitespace, there |
3527 |
// are (error) cases when it isn't |
// are (error) cases when it isn't |
3528 |
for (int i = 0; i < dataBufferPos; i++) { |
for (int i = 0; i < dataBufferPos; i++) |
3529 |
if (!isWhitespace (dataBuffer [i])) { |
{ |
3530 |
handler.charData (dataBuffer, 0, dataBufferPos); |
if (!isWhitespace(dataBuffer[i])) |
3531 |
dataBufferPos = 0; |
{ |
3532 |
} |
handler.charData(dataBuffer, 0, dataBufferPos); |
3533 |
} |
dataBufferPos = 0; |
3534 |
if (dataBufferPos > 0) { |
} |
3535 |
handler.ignorableWhitespace (dataBuffer, 0, dataBufferPos); |
} |
3536 |
dataBufferPos = 0; |
if (dataBufferPos > 0) |
3537 |
} |
{ |
3538 |
} else if (dataBufferPos > 0) { |
handler.ignorableWhitespace(dataBuffer, 0, dataBufferPos); |
3539 |
handler.charData (dataBuffer, 0, dataBufferPos); |
dataBufferPos = 0; |
3540 |
dataBufferPos = 0; |
} |
3541 |
} |
} |
3542 |
} |
else if (dataBufferPos > 0) |
3543 |
|
{ |
3544 |
|
handler.charData(dataBuffer, 0, dataBufferPos); |
3545 |
|
dataBufferPos = 0; |
3546 |
|
} |
3547 |
|
} |
3548 |
|
|
3549 |
/** |
/** |
3550 |
* Require a string to appear, or throw an exception. |
* Require a string to appear, or throw an exception. |
3551 |
* <p><em>Precondition:</em> Entity expansion is not required. |
* <p><em>Precondition:</em> Entity expansion is not required. |
3552 |
* <p><em>Precondition:</em> data buffer has no characters that |
* <p><em>Precondition:</em> data buffer has no characters that |
3553 |
* will get sent to the application. |
* will get sent to the application. |
3554 |
*/ |
*/ |
3555 |
private void require (String delim) |
private void require(String delim) |
3556 |
throws SAXException, IOException |
throws SAXException, IOException |
3557 |
{ |
{ |
3558 |
int length = delim.length (); |
int length = delim.length(); |
3559 |
char ch []; |
char[] ch; |
3560 |
|
|
3561 |
if (length < dataBuffer.length) { |
if (length < dataBuffer.length) |
3562 |
ch = dataBuffer; |
{ |
3563 |
delim.getChars (0, length, ch, 0); |
ch = dataBuffer; |
3564 |
} else |
delim.getChars(0, length, ch, 0); |
3565 |
ch = delim.toCharArray (); |
} |
3566 |
|
else |
3567 |
if (USE_CHEATS |
{ |
3568 |
&& length <= (readBufferLength - readBufferPos)) { |
ch = delim.toCharArray(); |
3569 |
int offset = readBufferPos; |
} |
3570 |
|
|
3571 |
for (int i = 0; i < length; i++, offset++) |
if (USE_CHEATS && length <= (readBufferLength - readBufferPos)) |
3572 |
if (ch [i] != readBuffer [offset]) |
{ |
3573 |
error ("required string", null, delim); |
int offset = readBufferPos; |
3574 |
readBufferPos = offset; |
|
3575 |
|
for (int i = 0; i < length; i++, offset++) |
3576 |
} else { |
{ |
3577 |
for (int i = 0; i < length; i++) |
if (ch[i] != readBuffer[offset]) |
3578 |
require (ch [i]); |
{ |
3579 |
} |
error ("required string", null, delim); |
3580 |
} |
} |
3581 |
|
} |
3582 |
|
readBufferPos = offset; |
3583 |
|
|
3584 |
|
} |
3585 |
|
else |
3586 |
|
{ |
3587 |
|
for (int i = 0; i < length; i++) |
3588 |
|
{ |
3589 |
|
require(ch[i]); |
3590 |
|
} |
3591 |
|
} |
3592 |
|
} |
3593 |
|
|
3594 |
/** |
/** |
3595 |
* Require a character to appear, or throw an exception. |
* Require a character to appear, or throw an exception. |
3596 |
*/ |
*/ |
3597 |
private void require (char delim) |
private void require(char delim) |
3598 |
throws SAXException, IOException |
throws SAXException, IOException |
3599 |
{ |
{ |
3600 |
char c = readCh (); |
char c = readCh(); |
3601 |
|
|
3602 |
if (c != delim) { |
if (c != delim) |
3603 |
error ("required character", c, new Character (delim).toString ()); |
{ |
3604 |
} |
error("required character", c, new Character(delim).toString()); |
3605 |
} |
} |
3606 |
|
} |
3607 |
|
|
3608 |
/** |
/** |
3609 |
* Create an interned string from a character array. |
* Create an interned string from a character array. |
3610 |
* Ælfred uses this method to create an interned version |
* Ælfred uses this method to create an interned version |
3611 |
* of all names and name tokens, so that it can test equality |
* of all names and name tokens, so that it can test equality |
3612 |
* with <code>==</code> instead of <code>String.equals ()</code>. |
* with <code>==</code> instead of <code>String.equals ()</code>. |
3613 |
* |
* |
3614 |
* <p>This is much more efficient than constructing a non-interned |
* <p>This is much more efficient than constructing a non-interned |
3615 |
* string first, and then interning it. |
* string first, and then interning it. |
3616 |
* |
* |
3617 |
* @param ch an array of characters for building the string. |
* @param ch an array of characters for building the string. |
3618 |
* @param start the starting position in the array. |
* @param start the starting position in the array. |
3619 |
* @param length the number of characters to place in the string. |
* @param length the number of characters to place in the string. |
3620 |
* @return an interned string. |
* @return an interned string. |
3621 |
* @see #intern (String) |
* @see #intern (String) |
3622 |
* @see java.lang.String#intern |
* @see java.lang.String#intern |
3623 |
*/ |
*/ |
3624 |
public String intern (char ch[], int start, int length) |
public String intern(char[] ch, int start, int length) |
3625 |
{ |
{ |
3626 |
int index = 0; |
int index = 0; |
3627 |
int hash = 0; |
int hash = 0; |
3628 |
Object bucket []; |
Object[] bucket; |
3629 |
|
|
3630 |
// Generate a hash code. This is a widely used string hash, |
// Generate a hash code. This is a widely used string hash, |
3631 |
// often attributed to Brian Kernighan. |
// often attributed to Brian Kernighan. |
3632 |
for (int i = start; i < start + length; i++) |
for (int i = start; i < start + length; i++) |
3633 |
hash = 31 * hash + ch [i]; |
{ |
3634 |
hash = (hash & 0x7fffffff) % SYMBOL_TABLE_LENGTH; |
hash = 31 * hash + ch[i]; |
3635 |
|
} |
3636 |
// Get the bucket -- consists of {array,String} pairs |
hash = (hash & 0x7fffffff) % SYMBOL_TABLE_LENGTH; |
3637 |
if ((bucket = symbolTable [hash]) == null) { |
|
3638 |
// first string in this bucket |
// Get the bucket -- consists of {array,String} pairs |
3639 |
bucket = new Object [8]; |
if ((bucket = symbolTable[hash]) == null) |
3640 |
|
{ |
3641 |
// Search for a matching tuple, and |
// first string in this bucket |
3642 |
// return the string if we find one. |
bucket = new Object[8]; |
3643 |
} else { |
|
3644 |
while (index < bucket.length) { |
// Search for a matching tuple, and |
3645 |
char chFound [] = (char []) bucket [index]; |
// return the string if we find one. |
3646 |
|
} |
3647 |
// Stop when we hit an empty entry. |
else |
3648 |
if (chFound == null) |
{ |
3649 |
break; |
while (index < bucket.length) |
3650 |
|
{ |
3651 |
// If they're the same length, check for a match. |
char[] chFound = (char[]) bucket[index]; |
3652 |
if (chFound.length == length) { |
|
3653 |
for (int i = 0; i < chFound.length; i++) { |
// Stop when we hit an empty entry. |
3654 |
// continue search on failure |
if (chFound == null) |
3655 |
if (ch [start + i] != chFound [i]) { |
{ |
3656 |
break; |
break; |
3657 |
} else if (i == length - 1) { |
} |
3658 |
// That's it, we have a match! |
|
3659 |
return (String) bucket [index + 1]; |
// If they're the same length, check for a match. |
3660 |
} |
if (chFound.length == length) |
3661 |
} |
{ |
3662 |
} |
for (int i = 0; i < chFound.length; i++) |
3663 |
index += 2; |
{ |
3664 |
} |
// continue search on failure |
3665 |
// Not found -- we'll have to add it. |
if (ch[start + i] != chFound[i]) |
3666 |
|
{ |
3667 |
// Do we have to grow the bucket? |
break; |
3668 |
bucket = (Object []) extendArray (bucket, bucket.length, index); |
} |
3669 |
} |
else if (i == length - 1) |
3670 |
symbolTable [hash] = bucket; |
{ |
3671 |
|
// That's it, we have a match! |
3672 |
// OK, add it to the end of the bucket -- "local" interning. |
return (String) bucket[index + 1]; |
3673 |
// Intern "globally" to let applications share interning benefits. |
} |
3674 |
// That is, "!=" and "==" work on our strings, not just equals(). |
} |
3675 |
String s = new String (ch, start, length).intern (); |
} |
3676 |
bucket [index] = s.toCharArray (); |
index += 2; |
3677 |
bucket [index + 1] = s; |
} |
3678 |
return s; |
// Not found -- we'll have to add it. |
3679 |
} |
|
3680 |
|
// Do we have to grow the bucket? |
3681 |
/** |
bucket = (Object[]) extendArray(bucket, bucket.length, index); |
3682 |
* Ensure the capacity of an array, allocating a new one if |
} |
3683 |
* necessary. Usually extends only for name hash collisions. |
symbolTable[hash] = bucket; |
3684 |
*/ |
|
3685 |
private Object extendArray (Object array, int currentSize, int requiredSize) |
// OK, add it to the end of the bucket -- "local" interning. |
3686 |
{ |
// Intern "globally" to let applications share interning benefits. |
3687 |
if (requiredSize < currentSize) { |
// That is, "!=" and "==" work on our strings, not just equals(). |
3688 |
return array; |
String s = new String(ch, start, length).intern(); |
3689 |
} else { |
bucket[index] = s.toCharArray(); |
3690 |
Object newArray = null; |
bucket[index + 1] = s; |
3691 |
int newSize = currentSize * 2; |
return s; |
3692 |
|
} |
|
if (newSize <= requiredSize) |
|
|
newSize = requiredSize + 1; |
|
|
|
|
|
if (array instanceof char[]) |
|
|
newArray = new char [newSize]; |
|
|
else if (array instanceof Object[]) |
|
|
newArray = new Object [newSize]; |
|
|
else |
|
|
throw new RuntimeException (); |
|
|
|
|
|
System.arraycopy (array, 0, newArray, 0, currentSize); |
|
|
return newArray; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
// XML query routines. |
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
boolean isStandalone () { return docIsStandalone; } |
|
|
|
|
|
|
|
|
// |
|
|
// Elements |
|
|
// |
|
|
|
|
|
private int getContentType (Object element [], int defaultType) |
|
|
{ |
|
|
int retval; |
|
|
|
|
|
if (element == null) |
|
|
return defaultType; |
|
|
retval = ((Integer) element [0]).intValue (); |
|
|
if (retval == CONTENT_UNDECLARED) |
|
|
retval = defaultType; |
|
|
return retval; |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Look up the content type of an element. |
|
|
* @param name The element type name. |
|
|
* @return An integer constant representing the content type. |
|
|
* @see #CONTENT_UNDECLARED |
|
|
* @see #CONTENT_ANY |
|
|
* @see #CONTENT_EMPTY |
|
|
* @see #CONTENT_MIXED |
|
|
* @see #CONTENT_ELEMENTS |
|
|
*/ |
|
|
public int getElementContentType (String name) |
|
|
{ |
|
|
Object element [] = (Object []) elementInfo.get (name); |
|
|
return getContentType (element, CONTENT_UNDECLARED); |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Register an element. |
|
|
* Array format: |
|
|
* [0] element type name |
|
|
* [1] content model (mixed, elements only) |
|
|
* [2] attribute hash table |
|
|
*/ |
|
|
private void setElement ( |
|
|
String name, |
|
|
int contentType, |
|
|
String contentModel, |
|
|
Hashtable attributes |
|
|
) throws SAXException |
|
|
{ |
|
|
if (skippedPE) |
|
|
return; |
|
|
|
|
|
Object element [] = (Object []) elementInfo.get (name); |
|
|
|
|
|
// first <!ELEMENT ...> or <!ATTLIST ...> for this type? |
|
|
if (element == null) { |
|
|
element = new Object [3]; |
|
|
element [0] = new Integer (contentType); |
|
|
element [1] = contentModel; |
|
|
element [2] = attributes; |
|
|
elementInfo.put (name, element); |
|
|
return; |
|
|
} |
|
|
|
|
|
// <!ELEMENT ...> declaration? |
|
|
if (contentType != CONTENT_UNDECLARED) { |
|
|
// ... following an associated <!ATTLIST ...> |
|
|
if (((Integer) element [0]).intValue () == CONTENT_UNDECLARED) { |
|
|
element [0] = new Integer (contentType); |
|
|
element [1] = contentModel; |
|
|
} else |
|
|
// VC: Unique Element Type Declaration |
|
|
handler.verror ("multiple declarations for element type: " |
|
|
+ name); |
|
|
} |
|
|
|
|
|
// first <!ATTLIST ...>, before <!ELEMENT ...> ? |
|
|
else if (attributes != null) |
|
|
element [2] = attributes; |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Look up the attribute hash table for an element. |
|
|
* The hash table is the second item in the element array. |
|
|
*/ |
|
|
private Hashtable getElementAttributes (String name) |
|
|
{ |
|
|
Object element[] = (Object[]) elementInfo.get (name); |
|
|
if (element == null) |
|
|
return null; |
|
|
else |
|
|
return (Hashtable) element [2]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// |
|
|
// Attributes |
|
|
// |
|
3693 |
|
|
3694 |
/** |
/** |
3695 |
* Get the declared attributes for an element type. |
* Ensure the capacity of an array, allocating a new one if |
3696 |
* @param elname The name of the element type. |
* necessary. Usually extends only for name hash collisions. |
3697 |
* @return An Enumeration of all the attributes declared for |
*/ |
3698 |
* a specific element type. The results will be valid only |
private Object extendArray(Object array, int currentSize, int requiredSize) |
3699 |
* after the DTD (if any) has been parsed. |
{ |
3700 |
* @see #getAttributeType |
if (requiredSize < currentSize) |
3701 |
* @see #getAttributeEnumeration |
{ |
3702 |
* @see #getAttributeDefaultValueType |
return array; |
3703 |
* @see #getAttributeDefaultValue |
} |
3704 |
* @see #getAttributeExpandedValue |
else |
3705 |
*/ |
{ |
3706 |
private Enumeration declaredAttributes (Object element []) |
Object newArray = null; |
3707 |
{ |
int newSize = currentSize * 2; |
3708 |
Hashtable attlist; |
|
3709 |
|
if (newSize <= requiredSize) |
3710 |
|
{ |
3711 |
|
newSize = requiredSize + 1; |
3712 |
|
} |
3713 |
|
|
3714 |
|
if (array instanceof char[]) |
3715 |
|
{ |
3716 |
|
newArray = new char[newSize]; |
3717 |
|
} |
3718 |
|
else if (array instanceof Object[]) |
3719 |
|
{ |
3720 |
|
newArray = new Object[newSize]; |
3721 |
|
} |
3722 |
|
else |
3723 |
|
{ |
3724 |
|
throw new RuntimeException(); |
3725 |
|
} |
3726 |
|
|
3727 |
|
System.arraycopy(array, 0, newArray, 0, currentSize); |
3728 |
|
return newArray; |
3729 |
|
} |
3730 |
|
} |
3731 |
|
|
3732 |
if (element == null) |
////////////////////////////////////////////////////////////////////// |
3733 |
return null; |
// XML query routines. |
3734 |
if ((attlist = (Hashtable) element [2]) == null) |
////////////////////////////////////////////////////////////////////// |
3735 |
return null; |
|
3736 |
return attlist.keys (); |
boolean isStandalone() |
3737 |
} |
{ |
3738 |
|
return docIsStandalone; |
3739 |
|
} |
3740 |
|
|
3741 |
|
// |
3742 |
|
// Elements |
3743 |
|
// |
3744 |
|
|
3745 |
|
private int getContentType(ElementDecl element, int defaultType) |
3746 |
|
{ |
3747 |
|
int retval; |
3748 |
|
|
3749 |
|
if (element == null) |
3750 |
|
{ |
3751 |
|
return defaultType; |
3752 |
|
} |
3753 |
|
retval = element.contentType; |
3754 |
|
if (retval == CONTENT_UNDECLARED) |
3755 |
|
{ |
3756 |
|
retval = defaultType; |
3757 |
|
} |
3758 |
|
return retval; |
3759 |
|
} |
3760 |
|
|
3761 |
/** |
/** |
3762 |
* Get the declared attributes for an element type. |
* Look up the content type of an element. |
3763 |
* @param elname The name of the element type. |
* @param name The element type name. |
3764 |
* @return An Enumeration of all the attributes declared for |
* @return An integer constant representing the content type. |
3765 |
* a specific element type. The results will be valid only |
* @see #CONTENT_UNDECLARED |
3766 |
* after the DTD (if any) has been parsed. |
* @see #CONTENT_ANY |
3767 |
* @see #getAttributeType |
* @see #CONTENT_EMPTY |
3768 |
* @see #getAttributeEnumeration |
* @see #CONTENT_MIXED |
3769 |
* @see #getAttributeDefaultValueType |
* @see #CONTENT_ELEMENTS |
3770 |
* @see #getAttributeDefaultValue |
*/ |
3771 |
* @see #getAttributeExpandedValue |
public int getElementContentType(String name) |
3772 |
*/ |
{ |
3773 |
public Enumeration declaredAttributes (String elname) |
ElementDecl element = (ElementDecl) elementInfo.get(name); |
3774 |
{ |
return getContentType(element, CONTENT_UNDECLARED); |
3775 |
return declaredAttributes ((Object []) elementInfo.get (elname)); |
} |
3776 |
} |
|
3777 |
|
/** |
3778 |
|
* Register an element. |
3779 |
|
* Array format: |
3780 |
|
* [0] element type name |
3781 |
|
* [1] content model (mixed, elements only) |
3782 |
|
* [2] attribute hash table |
3783 |
|
*/ |
3784 |
|
private void setElement(String name, int contentType, |
3785 |
|
String contentModel, HashMap attributes) |
3786 |
|
throws SAXException |
3787 |
|
{ |
3788 |
|
if (skippedPE) |
3789 |
|
{ |
3790 |
|
return; |
3791 |
|
} |
3792 |
|
|
3793 |
|
ElementDecl element = (ElementDecl) elementInfo.get(name); |
3794 |
|
|
3795 |
|
// first <!ELEMENT ...> or <!ATTLIST ...> for this type? |
3796 |
|
if (element == null) |
3797 |
|
{ |
3798 |
|
element = new ElementDecl(); |
3799 |
|
element.contentType = contentType; |
3800 |
|
element.contentModel = contentModel; |
3801 |
|
element.attributes = attributes; |
3802 |
|
elementInfo.put(name, element); |
3803 |
|
return; |
3804 |
|
} |
3805 |
|
|
3806 |
|
// <!ELEMENT ...> declaration? |
3807 |
|
if (contentType != CONTENT_UNDECLARED) |
3808 |
|
{ |
3809 |
|
// ... following an associated <!ATTLIST ...> |
3810 |
|
if (element.contentType == CONTENT_UNDECLARED) |
3811 |
|
{ |
3812 |
|
element.contentType = contentType; |
3813 |
|
element.contentModel = contentModel; |
3814 |
|
} |
3815 |
|
else |
3816 |
|
{ |
3817 |
|
// VC: Unique Element Type Declaration |
3818 |
|
handler.verror("multiple declarations for element type: " |
3819 |
|
+ name); |
3820 |
|
} |
3821 |
|
} |
3822 |
|
|
3823 |
|
// first <!ATTLIST ...>, before <!ELEMENT ...> ? |
3824 |
|
else if (attributes != null) |
3825 |
|
{ |
3826 |
|
element.attributes = attributes; |
3827 |
|
} |
3828 |
|
} |
3829 |
|
|
3830 |
|
/** |
3831 |
|
* Look up the attribute hash table for an element. |
3832 |
|
* The hash table is the second item in the element array. |
3833 |
|
*/ |
3834 |
|
private HashMap getElementAttributes(String name) |
3835 |
|
{ |
3836 |
|
ElementDecl element = (ElementDecl) elementInfo.get(name); |
3837 |
|
return (element == null) ? null : element.attributes; |
3838 |
|
} |
3839 |
|
|
3840 |
/** |
// |
3841 |
* Retrieve the declared type of an attribute. |
// Attributes |
3842 |
* @param name The name of the associated element. |
// |
3843 |
* @param aname The name of the attribute. |
|
3844 |
* @return An interend string denoting the type, or null |
/** |
3845 |
* indicating an undeclared attribute. |
* Get the declared attributes for an element type. |
3846 |
*/ |
* @param elname The name of the element type. |
3847 |
public String getAttributeType (String name, String aname) |
* @return An iterator over all the attributes declared for |
3848 |
{ |
* a specific element type. The results will be valid only |
3849 |
Object attribute[] = getAttribute (name, aname); |
* after the DTD (if any) has been parsed. |
3850 |
if (attribute == null) { |
* @see #getAttributeType |
3851 |
return null; |
* @see #getAttributeEnumeration |
3852 |
} else { |
* @see #getAttributeDefaultValueType |
3853 |
return (String) attribute [0]; |
* @see #getAttributeDefaultValue |
3854 |
} |
* @see #getAttributeExpandedValue |
3855 |
} |
*/ |
3856 |
|
private Iterator declaredAttributes(ElementDecl element) |
3857 |
|
{ |
3858 |
|
HashMap attlist; |
3859 |
|
|
3860 |
|
if (element == null) |
3861 |
|
{ |
3862 |
|
return null; |
3863 |
|
} |
3864 |
|
if ((attlist = element.attributes) == null) |
3865 |
|
{ |
3866 |
|
return null; |
3867 |
|
} |
3868 |
|
return attlist.keySet().iterator(); |
3869 |
|
} |
3870 |
|
|
3871 |
|
/** |
3872 |
|
* Get the declared attributes for an element type. |
3873 |
|
* @param elname The name of the element type. |
3874 |
|
* @return An iterator over all the attributes declared for |
3875 |
|
* a specific element type. The results will be valid only |
3876 |
|
* after the DTD (if any) has been parsed. |
3877 |
|
* @see #getAttributeType |
3878 |
|
* @see #getAttributeEnumeration |
3879 |
|
* @see #getAttributeDefaultValueType |
3880 |
|
* @see #getAttributeDefaultValue |
3881 |
|
* @see #getAttributeExpandedValue |
3882 |
|
*/ |
3883 |
|
public Iterator declaredAttributes(String elname) |
3884 |
|
{ |
3885 |
|
return declaredAttributes((ElementDecl) elementInfo.get(elname)); |
3886 |
|
} |
3887 |
|
|
3888 |
/** |
/** |
3889 |
* Retrieve the allowed values for an enumerated attribute type. |
* Retrieve the declared type of an attribute. |
3890 |
* @param name The name of the associated element. |
* @param name The name of the associated element. |
3891 |
* @param aname The name of the attribute. |
* @param aname The name of the attribute. |
3892 |
* @return A string containing the token list. |
* @return An interend string denoting the type, or null |
3893 |
*/ |
* indicating an undeclared attribute. |
3894 |
public String getAttributeEnumeration (String name, String aname) |
*/ |
3895 |
{ |
public String getAttributeType(String name, String aname) |
3896 |
Object attribute[] = getAttribute (name, aname); |
{ |
3897 |
if (attribute == null) { |
AttributeDecl attribute = getAttribute(name, aname); |
3898 |
return null; |
return (attribute == null) ? null : attribute.type; |
3899 |
} else { |
} |
|
// assert: attribute [0] is "ENUMERATION" or "NOTATION" |
|
|
return (String) attribute [3]; |
|
|
} |
|
|
} |
|
3900 |
|
|
3901 |
|
/** |
3902 |
|
* Retrieve the allowed values for an enumerated attribute type. |
3903 |
|
* @param name The name of the associated element. |
3904 |
|
* @param aname The name of the attribute. |
3905 |
|
* @return A string containing the token list. |
3906 |
|
*/ |
3907 |
|
public String getAttributeEnumeration(String name, String aname) |
3908 |
|
{ |
3909 |
|
AttributeDecl attribute = getAttribute(name, aname); |
3910 |
|
// assert: attribute.enumeration is "ENUMERATION" or "NOTATION" |
3911 |
|
return (attribute == null) ? null : attribute.enumeration; |
3912 |
|
} |
3913 |
|
|
3914 |
/** |
/** |
3915 |
* Retrieve the default value of a declared attribute. |
* Retrieve the default value of a declared attribute. |
3916 |
* @param name The name of the associated element. |
* @param name The name of the associated element. |
3917 |
* @param aname The name of the attribute. |
* @param aname The name of the attribute. |
3918 |
* @return The default value, or null if the attribute was |
* @return The default value, or null if the attribute was |
3919 |
* #IMPLIED or simply undeclared and unspecified. |
* #IMPLIED or simply undeclared and unspecified. |
3920 |
* @see #getAttributeExpandedValue |
* @see #getAttributeExpandedValue |
3921 |
*/ |
*/ |
3922 |
public String getAttributeDefaultValue (String name, String aname) |
public String getAttributeDefaultValue(String name, String aname) |
3923 |
{ |
{ |
3924 |
Object attribute[] = getAttribute (name, aname); |
AttributeDecl attribute = getAttribute(name, aname); |
3925 |
if (attribute == null) { |
return (attribute == null) ? null : attribute.value; |
3926 |
return null; |
} |
|
} else { |
|
|
return (String) attribute [1]; |
|
|
} |
|
|
} |
|
3927 |
|
|
3928 |
/* |
/* |
3929 |
|
|
3940 |
* @param name The name of the associated element. |
* @param name The name of the associated element. |
3941 |
* @param aname The name of the attribute. |
* @param aname The name of the attribute. |
3942 |
* @return The expanded default value, or null if the attribute was |
* @return The expanded default value, or null if the attribute was |
3943 |
* #IMPLIED or simply undeclared |
* #IMPLIED or simply undeclared |
3944 |
* @see #getAttributeDefaultValue |
* @see #getAttributeDefaultValue |
3945 |
public String getAttributeExpandedValue (String name, String aname) |
public String getAttributeExpandedValue (String name, String aname) |
3946 |
throws Exception |
throws Exception |
3947 |
{ |
{ |
3948 |
Object attribute[] = getAttribute (name, aname); |
AttributeDecl attribute = getAttribute (name, aname); |
3949 |
|
|
3950 |
if (attribute == null) { |
if (attribute == null) { |
3951 |
return null; |
return null; |
3952 |
} else if (attribute [4] == null && attribute [1] != null) { |
} else if (attribute.defaultValue == null && attribute.value != null) { |
3953 |
// we MUST use the same buf for both quotes else the literal |
// we MUST use the same buf for both quotes else the literal |
3954 |
// can't be properly terminated |
// can't be properly terminated |
3955 |
char buf [] = new char [1]; |
char buf [] = new char [1]; |
3956 |
int flags = LIT_ENTITY_REF | LIT_ATTRIBUTE; |
int flags = LIT_ENTITY_REF | LIT_ATTRIBUTE; |
3957 |
String type = getAttributeType (name, aname); |
String type = getAttributeType (name, aname); |
3958 |
|
|
3959 |
if (type != "CDATA" && type != null) |
if (type != "CDATA" && type != null) |
3960 |
flags |= LIT_NORMALIZE; |
flags |= LIT_NORMALIZE; |
3961 |
buf [0] = '"'; |
buf [0] = '"'; |
3962 |
pushCharArray (null, buf, 0, 1); |
pushCharArray (null, buf, 0, 1); |
3963 |
pushString (null, (String) attribute [1]); |
pushString (null, attribute.value); |
3964 |
pushCharArray (null, buf, 0, 1); |
pushCharArray (null, buf, 0, 1); |
3965 |
attribute [4] = readLiteral (flags); |
attribute.defaultValue = readLiteral (flags); |
3966 |
} |
} |
3967 |
return (String) attribute [4]; |
return attribute.defaultValue; |
3968 |
} |
} |
3969 |
*/ |
*/ |
3970 |
|
|
3971 |
/** |
/** |
3972 |
* Retrieve the default value mode of a declared attribute. |
* Retrieve the default value mode of a declared attribute. |
3973 |
* @see #ATTRIBUTE_DEFAULT_SPECIFIED |
* @see #ATTRIBUTE_DEFAULT_SPECIFIED |
3974 |
* @see #ATTRIBUTE_DEFAULT_IMPLIED |
* @see #ATTRIBUTE_DEFAULT_IMPLIED |
3975 |
* @see #ATTRIBUTE_DEFAULT_REQUIRED |
* @see #ATTRIBUTE_DEFAULT_REQUIRED |
3976 |
* @see #ATTRIBUTE_DEFAULT_FIXED |
* @see #ATTRIBUTE_DEFAULT_FIXED |
3977 |
*/ |
*/ |
3978 |
public int getAttributeDefaultValueType (String name, String aname) |
public int getAttributeDefaultValueType(String name, String aname) |
3979 |
{ |
{ |
3980 |
Object attribute[] = getAttribute (name, aname); |
AttributeDecl attribute = getAttribute(name, aname); |
3981 |
if (attribute == null) { |
return (attribute == null) ? ATTRIBUTE_DEFAULT_UNDECLARED : |
3982 |
return ATTRIBUTE_DEFAULT_UNDECLARED; |
attribute.valueType; |
3983 |
} else { |
} |
3984 |
return ((Integer) attribute [2]).intValue (); |
|
3985 |
} |
/** |
3986 |
} |
* Register an attribute declaration for later retrieval. |
3987 |
|
* Format: |
3988 |
|
* - String type |
3989 |
/** |
* - String default value |
3990 |
* Register an attribute declaration for later retrieval. |
* - int value type |
3991 |
* Format: |
* - enumeration |
3992 |
* - String type |
* - processed default value |
3993 |
* - String default value |
*/ |
3994 |
* - int value type |
private void setAttribute(String elName, String name, String type, |
3995 |
* - enumeration |
String enumeration, String value, int valueType) |
|
* - processed default value |
|
|
*/ |
|
|
private void setAttribute (String elName, String name, String type, |
|
|
String enumeration, |
|
|
String value, int valueType) |
|
3996 |
throws Exception |
throws Exception |
3997 |
{ |
{ |
3998 |
Hashtable attlist; |
HashMap attlist; |
3999 |
|
|
4000 |
if (skippedPE) |
if (skippedPE) |
4001 |
return; |
{ |
4002 |
|
return; |
4003 |
// Create a new hashtable if necessary. |
} |
4004 |
attlist = getElementAttributes (elName); |
|
4005 |
if (attlist == null) |
// Create a new hashtable if necessary. |
4006 |
attlist = new Hashtable (); |
attlist = getElementAttributes(elName); |
4007 |
|
if (attlist == null) |
4008 |
// ignore multiple attribute declarations! |
{ |
4009 |
if (attlist.get (name) != null) { |
attlist = new HashMap(); |
4010 |
// warn ... |
} |
4011 |
return; |
|
4012 |
} else { |
// ignore multiple attribute declarations! |
4013 |
Object attribute [] = new Object [5]; |
if (attlist.get(name) != null) |
4014 |
attribute [0] = type; |
{ |
4015 |
attribute [1] = value; |
// warn ... |
4016 |
attribute [2] = new Integer (valueType); |
return; |
4017 |
attribute [3] = enumeration; |
} |
4018 |
attribute [4] = null; |
else |
4019 |
attlist.put (name, attribute); |
{ |
4020 |
|
AttributeDecl attribute = new AttributeDecl(); |
4021 |
// save; but don't overwrite any existing <!ELEMENT ...> |
attribute.type = type; |
4022 |
setElement (elName, CONTENT_UNDECLARED, null, attlist); |
attribute.value = value; |
4023 |
} |
attribute.valueType = valueType; |
4024 |
} |
attribute.enumeration = enumeration; |
4025 |
|
attlist.put(name, attribute); |
4026 |
|
|
4027 |
/** |
// save; but don't overwrite any existing <!ELEMENT ...> |
4028 |
* Retrieve the array representing an attribute declaration. |
setElement(elName, CONTENT_UNDECLARED, null, attlist); |
4029 |
*/ |
} |
4030 |
private Object[] getAttribute (String elName, String name) |
} |
|
{ |
|
|
Hashtable attlist; |
|
|
|
|
|
attlist = getElementAttributes (elName); |
|
|
if (attlist == null) |
|
|
return null; |
|
|
return (Object[]) attlist.get (name); |
|
|
} |
|
|
|
|
|
|
|
|
// |
|
|
// Entities |
|
|
// |
|
|
|
|
|
/** |
|
|
* Find the type of an entity. |
|
|
* @returns An integer constant representing the entity type. |
|
|
* @see #ENTITY_UNDECLARED |
|
|
* @see #ENTITY_INTERNAL |
|
|
* @see #ENTITY_NDATA |
|
|
* @see #ENTITY_TEXT |
|
|
*/ |
|
|
public int getEntityType (String ename) |
|
|
{ |
|
|
Object entity[] = (Object[]) entityInfo.get (ename); |
|
|
if (entity == null) { |
|
|
return ENTITY_UNDECLARED; |
|
|
} else { |
|
|
return ((Integer) entity [0]).intValue (); |
|
|
} |
|
|
} |
|
|
|
|
4031 |
|
|
4032 |
/** |
/** |
4033 |
* Return an external entity's identifier array. |
* Retrieve the attribute declaration for the given element name and name. |
4034 |
* @param ename The name of the external entity. |
*/ |
4035 |
* @return Three element array containing (in order) the entity's |
private AttributeDecl getAttribute(String elName, String name) |
4036 |
* public identifier, system identifier, and base URI. Null if |
{ |
4037 |
* the entity was not declared as an external entity. |
HashMap attlist = getElementAttributes(elName); |
4038 |
* @see #getEntityType |
return (attlist == null) ? null : (AttributeDecl) attlist.get(name); |
4039 |
*/ |
} |
|
public String [] getEntityIds (String ename) |
|
|
{ |
|
|
Object entity[] = (Object[]) entityInfo.get (ename); |
|
|
if (entity == null) { |
|
|
return null; |
|
|
} else { |
|
|
return (String []) entity [1]; |
|
|
} |
|
|
} |
|
4040 |
|
|
4041 |
|
// |
4042 |
|
// Entities |
4043 |
|
// |
4044 |
|
|
4045 |
|
/** |
4046 |
|
* Find the type of an entity. |
4047 |
|
* @returns An integer constant representing the entity type. |
4048 |
|
* @see #ENTITY_UNDECLARED |
4049 |
|
* @see #ENTITY_INTERNAL |
4050 |
|
* @see #ENTITY_NDATA |
4051 |
|
* @see #ENTITY_TEXT |
4052 |
|
*/ |
4053 |
|
public int getEntityType(String ename) |
4054 |
|
{ |
4055 |
|
EntityInfo entity = (EntityInfo) entityInfo.get(ename); |
4056 |
|
return (entity == null) ? ENTITY_UNDECLARED : entity.type; |
4057 |
|
} |
4058 |
|
|
4059 |
/** |
/** |
4060 |
* Return an internal entity's replacement text. |
* Return an external entity's identifiers. |
4061 |
* @param ename The name of the internal entity. |
* @param ename The name of the external entity. |
4062 |
* @return The entity's replacement text, or null if |
* @return The entity's public identifier, system identifier, and base URI. |
4063 |
* the entity was not declared as an internal entity. |
* Null if the entity was not declared as an external entity. |
4064 |
* @see #getEntityType |
* @see #getEntityType |
4065 |
*/ |
*/ |
4066 |
public String getEntityValue (String ename) |
public ExternalIdentifiers getEntityIds(String ename) |
4067 |
{ |
{ |
4068 |
Object entity[] = (Object[]) entityInfo.get (ename); |
EntityInfo entity = (EntityInfo) entityInfo.get(ename); |
4069 |
if (entity == null) { |
return (entity == null) ? null : entity.ids; |
4070 |
return null; |
} |
|
} else { |
|
|
return (String) entity [3]; |
|
|
} |
|
|
} |
|
4071 |
|
|
4072 |
|
/** |
4073 |
|
* Return an internal entity's replacement text. |
4074 |
|
* @param ename The name of the internal entity. |
4075 |
|
* @return The entity's replacement text, or null if |
4076 |
|
* the entity was not declared as an internal entity. |
4077 |
|
* @see #getEntityType |
4078 |
|
*/ |
4079 |
|
public String getEntityValue(String ename) |
4080 |
|
{ |
4081 |
|
EntityInfo entity = (EntityInfo) entityInfo.get(ename); |
4082 |
|
return (entity == null) ? null : entity.value; |
4083 |
|
} |
4084 |
|
|
4085 |
/** |
/** |
4086 |
* Register an entity declaration for later retrieval. |
* Register an entity declaration for later retrieval. |
4087 |
*/ |
*/ |
4088 |
private void setInternalEntity (String eName, String value) |
private void setInternalEntity(String eName, String value) |
4089 |
throws SAXException |
throws SAXException |
4090 |
{ |
{ |
4091 |
if (skippedPE) |
if (skippedPE) |
4092 |
return; |
{ |
4093 |
|
return; |
4094 |
|
} |
4095 |
|
|
4096 |
if (entityInfo.get (eName) == null) { |
if (entityInfo.get(eName) == null) |
4097 |
Object entity[] = new Object [5]; |
{ |
4098 |
entity [0] = new Integer (ENTITY_INTERNAL); |
EntityInfo entity = new EntityInfo(); |
4099 |
// FIXME: shrink!! [2] useless |
entity.type = ENTITY_INTERNAL; |
4100 |
entity [3] = value; |
entity.value = value; |
4101 |
entityInfo.put (eName, entity); |
entityInfo.put(eName, entity); |
4102 |
} |
} |
4103 |
if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { |
if (handler.stringInterning) |
4104 |
if ("lt" == eName || "gt" == eName || "quot" == eName |
{ |
4105 |
|| "apos" == eName || "amp" == eName) |
if ("lt" == eName || "gt" == eName || "quot" == eName |
4106 |
return; |
|| "apos" == eName || "amp" == eName) |
4107 |
} else { |
{ |
4108 |
if ("lt".equals(eName) || "gt".equals(eName) || "quot".equals(eName) |
return; |
4109 |
|| "apos".equals(eName) || "amp".equals(eName)) |
} |
4110 |
return; |
} |
4111 |
|
else |
4112 |
|
{ |
4113 |
|
if ("lt".equals(eName) || "gt".equals(eName) || "quot".equals(eName) |
4114 |
|
|| "apos".equals(eName) || "amp".equals(eName)) |
4115 |
|
{ |
4116 |
|
return; |
4117 |
|
} |
4118 |
|
} |
4119 |
|
handler.getDeclHandler().internalEntityDecl(eName, value); |
4120 |
} |
} |
|
handler.getDeclHandler () |
|
|
.internalEntityDecl (eName, value); |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
* Register an external entity declaration for later retrieval. |
|
|
*/ |
|
|
private void setExternalEntity (String eName, int eClass, |
|
|
String ids [], String nName) |
|
|
{ |
|
|
if (entityInfo.get (eName) == null) { |
|
|
Object entity[] = new Object [5]; |
|
|
entity [0] = new Integer (eClass); |
|
|
entity [1] = ids; |
|
|
// FIXME: shrink!! [2] no longer used, [4] irrelevant given [0] |
|
|
entity [4] = nName; |
|
|
entityInfo.put (eName, entity); |
|
|
} |
|
|
} |
|
|
|
|
4121 |
|
|
4122 |
// |
/** |
4123 |
// Notations. |
* Register an external entity declaration for later retrieval. |
4124 |
// |
*/ |
4125 |
|
private void setExternalEntity(String eName, int eClass, |
4126 |
|
ExternalIdentifiers ids, String nName) |
4127 |
|
{ |
4128 |
|
if (entityInfo.get(eName) == null) |
4129 |
|
{ |
4130 |
|
EntityInfo entity = new EntityInfo(); |
4131 |
|
entity.type = eClass; |
4132 |
|
entity.ids = ids; |
4133 |
|
entity.notationName = nName; |
4134 |
|
entityInfo.put(eName, entity); |
4135 |
|
} |
4136 |
|
} |
4137 |
|
|
4138 |
/** |
// |
4139 |
* Report a notation declaration, checking for duplicates. |
// Notations. |
4140 |
*/ |
// |
4141 |
private void setNotation (String nname, String ids []) |
|
4142 |
|
/** |
4143 |
|
* Report a notation declaration, checking for duplicates. |
4144 |
|
*/ |
4145 |
|
private void setNotation(String nname, ExternalIdentifiers ids) |
4146 |
throws SAXException |
throws SAXException |
4147 |
{ |
{ |
4148 |
if (skippedPE) |
if (skippedPE) |
4149 |
return; |
{ |
4150 |
|
return; |
4151 |
handler.notationDecl (nname, ids); |
} |
4152 |
if (notationInfo.get (nname) == null) |
|
4153 |
notationInfo.put (nname, nname); |
handler.notationDecl(nname, ids.publicId, ids.systemId, ids.baseUri); |
4154 |
else |
if (notationInfo.get(nname) == null) |
4155 |
// VC: Unique Notation Name |
{ |
4156 |
handler.verror ("Duplicate notation name decl: " + nname); |
notationInfo.put(nname, nname); |
4157 |
} |
} |
4158 |
|
else |
4159 |
|
{ |
4160 |
|
// VC: Unique Notation Name |
4161 |
|
handler.verror("Duplicate notation name decl: " + nname); |
4162 |
|
} |
4163 |
|
} |
4164 |
|
|
4165 |
|
// |
4166 |
|
// Location. |
4167 |
|
// |
4168 |
|
|
4169 |
|
/** |
4170 |
|
* Return the current line number. |
4171 |
|
*/ |
4172 |
|
public int getLineNumber() |
4173 |
|
{ |
4174 |
|
return line; |
4175 |
|
} |
4176 |
|
|
4177 |
// |
/** |
4178 |
// Location. |
* Return the current column number. |
4179 |
// |
*/ |
4180 |
|
public int getColumnNumber() |
4181 |
|
{ |
4182 |
|
return column; |
4183 |
|
} |
4184 |
|
|
4185 |
|
////////////////////////////////////////////////////////////////////// |
4186 |
|
// High-level I/O. |
4187 |
|
////////////////////////////////////////////////////////////////////// |
4188 |
|
|
4189 |
|
/** |
4190 |
|
* Read a single character from the readBuffer. |
4191 |
|
* <p>The readDataChunk () method maintains the buffer. |
4192 |
|
* <p>If we hit the end of an entity, try to pop the stack and |
4193 |
|
* keep going. |
4194 |
|
* <p> (This approach doesn't really enforce XML's rules about |
4195 |
|
* entity boundaries, but this is not currently a validating |
4196 |
|
* parser). |
4197 |
|
* <p>This routine also attempts to keep track of the current |
4198 |
|
* position in external entities, but it's not entirely accurate. |
4199 |
|
* @return The next available input character. |
4200 |
|
* @see #unread (char) |
4201 |
|
* @see #readDataChunk |
4202 |
|
* @see #readBuffer |
4203 |
|
* @see #line |
4204 |
|
* @return The next character from the current input source. |
4205 |
|
*/ |
4206 |
|
private char readCh() |
4207 |
|
throws SAXException, IOException |
4208 |
|
{ |
4209 |
|
// As long as there's nothing in the |
4210 |
|
// read buffer, try reading more data |
4211 |
|
// (for an external entity) or popping |
4212 |
|
// the entity stack (for either). |
4213 |
|
while (readBufferPos >= readBufferLength) |
4214 |
|
{ |
4215 |
|
switch (sourceType) |
4216 |
|
{ |
4217 |
|
case INPUT_READER: |
4218 |
|
case INPUT_STREAM: |
4219 |
|
readDataChunk(); |
4220 |
|
while (readBufferLength < 1) |
4221 |
|
{ |
4222 |
|
popInput(); |
4223 |
|
if (readBufferLength < 1) |
4224 |
|
{ |
4225 |
|
readDataChunk(); |
4226 |
|
} |
4227 |
|
} |
4228 |
|
break; |
4229 |
|
|
4230 |
|
default: |
4231 |
|
|
4232 |
|
popInput(); |
4233 |
|
break; |
4234 |
|
} |
4235 |
|
} |
4236 |
|
|
4237 |
|
char c = readBuffer[readBufferPos++]; |
4238 |
|
|
4239 |
|
if (c == '\n') |
4240 |
|
{ |
4241 |
|
line++; |
4242 |
|
column = 0; |
4243 |
|
} |
4244 |
|
else |
4245 |
|
{ |
4246 |
|
if (c == '<') |
4247 |
|
{ |
4248 |
|
/* the most common return to parseContent () ... NOP */ |
4249 |
|
} |
4250 |
|
else if (((c < 0x0020 && (c != '\t') && (c != '\r')) || c > 0xFFFD) |
4251 |
|
|| ((c >= 0x007f) && (c <= 0x009f) && (c != 0x0085) |
4252 |
|
&& xmlVersion == XML_11)) |
4253 |
|
{ |
4254 |
|
error("illegal XML character U+" + Integer.toHexString(c)); |
4255 |
|
} |
4256 |
|
|
4257 |
/** |
// If we're in the DTD and in a context where PEs get expanded, |
4258 |
* Return the current line number. |
// do so ... 1/14/2000 errata identify those contexts. There |
4259 |
*/ |
// are also spots in the internal subset where PE refs are fatal |
4260 |
public int getLineNumber () |
// errors, hence yet another flag. |
4261 |
{ |
else if (c == '%' && expandPE) |
4262 |
return line; |
{ |
4263 |
} |
if (peIsError) |
4264 |
|
{ |
4265 |
|
error("PE reference within decl in internal subset."); |
4266 |
|
} |
4267 |
|
parsePEReference(); |
4268 |
|
return readCh(); |
4269 |
|
} |
4270 |
|
column++; |
4271 |
|
} |
4272 |
|
|
4273 |
|
return c; |
4274 |
|
} |
4275 |
|
|
4276 |
/** |
/** |
4277 |
* Return the current column number. |
* Push a single character back onto the current input stream. |
4278 |
*/ |
* <p>This method usually pushes the character back onto |
4279 |
public int getColumnNumber () |
* the readBuffer. |
4280 |
{ |
* <p>I don't think that this would ever be called with |
4281 |
return column; |
* readBufferPos = 0, because the methods always reads a character |
4282 |
} |
* before unreading it, but just in case, I've added a boundary |
4283 |
|
* condition. |
4284 |
|
* @param c The character to push back. |
4285 |
|
* @see #readCh |
4286 |
|
* @see #unread (char[]) |
4287 |
|
* @see #readBuffer |
4288 |
|
*/ |
4289 |
|
private void unread(char c) |
4290 |
|
throws SAXException |
4291 |
|
{ |
4292 |
|
// Normal condition. |
4293 |
|
if (c == '\n') |
4294 |
|
{ |
4295 |
|
line--; |
4296 |
|
column = -1; |
4297 |
|
} |
4298 |
|
if (readBufferPos > 0) |
4299 |
|
{ |
4300 |
|
readBuffer[--readBufferPos] = c; |
4301 |
|
} |
4302 |
|
else |
4303 |
|
{ |
4304 |
|
pushString(null, new Character(c).toString()); |
4305 |
|
} |
4306 |
|
} |
4307 |
|
|
4308 |
|
/** |
4309 |
|
* Push a char array back onto the current input stream. |
4310 |
|
* <p>NOTE: you must <em>never</em> push back characters that you |
4311 |
|
* haven't actually read: use pushString () instead. |
4312 |
|
* @see #readCh |
4313 |
|
* @see #unread (char) |
4314 |
|
* @see #readBuffer |
4315 |
|
* @see #pushString |
4316 |
|
*/ |
4317 |
|
private void unread(char[] ch, int length) |
4318 |
|
throws SAXException |
4319 |
|
{ |
4320 |
|
for (int i = 0; i < length; i++) |
4321 |
|
{ |
4322 |
|
if (ch[i] == '\n') |
4323 |
|
{ |
4324 |
|
line--; |
4325 |
|
column = -1; |
4326 |
|
} |
4327 |
|
} |
4328 |
|
if (length < readBufferPos) |
4329 |
|
{ |
4330 |
|
readBufferPos -= length; |
4331 |
|
} |
4332 |
|
else |
4333 |
|
{ |
4334 |
|
pushCharArray(null, ch, 0, length); |
4335 |
|
} |
4336 |
|
} |
4337 |
|
|
4338 |
////////////////////////////////////////////////////////////////////// |
/** |
4339 |
// High-level I/O. |
* Push, or skip, a new external input source. |
4340 |
////////////////////////////////////////////////////////////////////// |
* The source will be some kind of parsed entity, such as a PE |
4341 |
|
* (including the external DTD subset) or content for the body. |
4342 |
|
* |
4343 |
/** |
* @param url The java.net.URL object for the entity. |
4344 |
* Read a single character from the readBuffer. |
* @see SAXDriver#resolveEntity |
4345 |
* <p>The readDataChunk () method maintains the buffer. |
* @see #pushString |
4346 |
* <p>If we hit the end of an entity, try to pop the stack and |
* @see #sourceType |
4347 |
* keep going. |
* @see #pushInput |
4348 |
* <p> (This approach doesn't really enforce XML's rules about |
* @see #detectEncoding |
4349 |
* entity boundaries, but this is not currently a validating |
* @see #sourceType |
4350 |
* parser). |
* @see #readBuffer |
4351 |
* <p>This routine also attempts to keep track of the current |
*/ |
4352 |
* position in external entities, but it's not entirely accurate. |
private void pushURL(boolean isPE, |
4353 |
* @return The next available input character. |
String ename, |
4354 |
* @see #unread (char) |
ExternalIdentifiers ids, |
4355 |
* @see #readDataChunk |
Reader reader, |
4356 |
* @see #readBuffer |
InputStream stream, |
4357 |
* @see #line |
String encoding, |
4358 |
* @return The next character from the current input source. |
boolean doResolve) |
|
*/ |
|
|
private char readCh () |
|
4359 |
throws SAXException, IOException |
throws SAXException, IOException |
4360 |
{ |
{ |
4361 |
// As long as there's nothing in the |
boolean ignoreEncoding; |
4362 |
// read buffer, try reading more data |
String systemId; |
4363 |
// (for an external entity) or popping |
InputSource source; |
4364 |
// the entity stack (for either). |
|
4365 |
while (readBufferPos >= readBufferLength) { |
if (!isPE) |
4366 |
switch (sourceType) { |
{ |
4367 |
case INPUT_READER: |
dataBufferFlush(); |
4368 |
case INPUT_STREAM: |
} |
|
readDataChunk (); |
|
|
while (readBufferLength < 1) { |
|
|
popInput (); |
|
|
if (readBufferLength < 1) { |
|
|
readDataChunk (); |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
popInput (); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
char c = readBuffer [readBufferPos++]; |
|
|
|
|
|
if (c == '\n') { |
|
|
line++; |
|
|
column = 0; |
|
|
} else { |
|
|
if (c == '<') { |
|
|
/* the most common return to parseContent () ... NOP */ |
|
|
} else if (((c < 0x0020 && (c != '\t') && (c != '\r')) || c > 0xFFFD) |
|
|
|| ((c >= 0x007f) && (c <= 0x009f) && (c != 0x0085) |
|
|
&& xmlVersion == XML_11)) |
|
|
error ("illegal XML character U+" |
|
|
+ Integer.toHexString (c)); |
|
|
|
|
|
// If we're in the DTD and in a context where PEs get expanded, |
|
|
// do so ... 1/14/2000 errata identify those contexts. There |
|
|
// are also spots in the internal subset where PE refs are fatal |
|
|
// errors, hence yet another flag. |
|
|
else if (c == '%' && expandPE) { |
|
|
if (peIsError) |
|
|
error ("PE reference within decl in internal subset."); |
|
|
parsePEReference (); |
|
|
return readCh (); |
|
|
} |
|
|
column++; |
|
|
} |
|
|
|
|
|
return c; |
|
|
} |
|
4369 |
|
|
4370 |
|
scratch.setPublicId(ids.publicId); |
4371 |
|
scratch.setSystemId(ids.systemId); |
4372 |
|
|
4373 |
/** |
// See if we should skip or substitute the entity. |
4374 |
* Push a single character back onto the current input stream. |
// If we're not skipping, resolving reports startEntity() |
4375 |
* <p>This method usually pushes the character back onto |
// and updates the (handler's) stack of URIs. |
4376 |
* the readBuffer. |
if (doResolve) |
4377 |
* <p>I don't think that this would ever be called with |
{ |
4378 |
* readBufferPos = 0, because the methods always reads a character |
// assert (stream == null && reader == null && encoding == null) |
4379 |
* before unreading it, but just in case, I've added a boundary |
source = handler.resolveEntity(isPE, ename, scratch, ids.baseUri); |
4380 |
* condition. |
if (source == null) |
4381 |
* @param c The character to push back. |
{ |
4382 |
* @see #readCh |
handler.warn("skipping entity: " + ename); |
4383 |
* @see #unread (char[]) |
handler.skippedEntity(ename); |
4384 |
* @see #readBuffer |
if (isPE) |
4385 |
*/ |
{ |
4386 |
private void unread (char c) |
skippedPE = true; |
4387 |
throws SAXException |
} |
4388 |
{ |
return; |
4389 |
// Normal condition. |
} |
|
if (c == '\n') { |
|
|
line--; |
|
|
column = -1; |
|
|
} |
|
|
if (readBufferPos > 0) { |
|
|
readBuffer [--readBufferPos] = c; |
|
|
} else { |
|
|
pushString (null, new Character (c).toString ()); |
|
|
} |
|
|
} |
|
4390 |
|
|
4391 |
|
// we might be using alternate IDs/encoding |
4392 |
|
systemId = source.getSystemId(); |
4393 |
|
// The following warning and setting systemId was deleted bcause |
4394 |
|
// the application has the option of not setting systemId |
4395 |
|
// provided that it has set the characte/byte stream. |
4396 |
|
/* |
4397 |
|
if (systemId == null) { |
4398 |
|
handler.warn ("missing system ID, using " + ids.systemId); |
4399 |
|
systemId = ids.systemId; |
4400 |
|
} |
4401 |
|
*/ |
4402 |
|
} |
4403 |
|
else |
4404 |
|
{ |
4405 |
|
// "[document]", or "[dtd]" via getExternalSubset() |
4406 |
|
scratch.setCharacterStream(reader); |
4407 |
|
scratch.setByteStream(stream); |
4408 |
|
scratch.setEncoding(encoding); |
4409 |
|
source = scratch; |
4410 |
|
systemId = ids.systemId; |
4411 |
|
if (handler.stringInterning) |
4412 |
|
{ |
4413 |
|
handler.startExternalEntity(ename, systemId, |
4414 |
|
"[document]" == ename); |
4415 |
|
} |
4416 |
|
else |
4417 |
|
{ |
4418 |
|
handler.startExternalEntity(ename, systemId, |
4419 |
|
"[document]".equals(ename)); |
4420 |
|
} |
4421 |
|
} |
4422 |
|
|
4423 |
/** |
// we may have been given I/O streams directly |
4424 |
* Push a char array back onto the current input stream. |
if (source.getCharacterStream() != null) |
4425 |
* <p>NOTE: you must <em>never</em> push back characters that you |
{ |
4426 |
* haven't actually read: use pushString () instead. |
if (source.getByteStream() != null) |
4427 |
* @see #readCh |
error("InputSource has two streams!"); |
4428 |
* @see #unread (char) |
reader = source.getCharacterStream(); |
4429 |
* @see #readBuffer |
} |
4430 |
* @see #pushString |
else if (source.getByteStream() != null) |
4431 |
*/ |
{ |
4432 |
private void unread (char ch[], int length) |
encoding = source.getEncoding(); |
4433 |
throws SAXException |
if (encoding == null) |
4434 |
{ |
{ |
4435 |
for (int i = 0; i < length; i++) { |
stream = source.getByteStream(); |
4436 |
if (ch [i] == '\n') { |
} |
4437 |
line--; |
else |
4438 |
column = -1; |
{ |
4439 |
} |
try |
4440 |
} |
{ |
4441 |
if (length < readBufferPos) { |
reader = new InputStreamReader(source.getByteStream(), |
4442 |
readBufferPos -= length; |
encoding); |
4443 |
} else { |
} |
4444 |
pushCharArray (null, ch, 0, length); |
catch (IOException e) |
4445 |
} |
{ |
4446 |
} |
stream = source.getByteStream(); |
4447 |
|
} |
4448 |
|
} |
4449 |
|
} |
4450 |
|
else if (systemId == null) |
4451 |
|
{ |
4452 |
|
error("InputSource has no URI!"); |
4453 |
|
} |
4454 |
|
scratch.setCharacterStream(null); |
4455 |
|
scratch.setByteStream(null); |
4456 |
|
scratch.setEncoding(null); |
4457 |
|
|
4458 |
|
// Push the existing status. |
4459 |
|
pushInput(ename); |
4460 |
|
|
4461 |
|
// Create a new read buffer. |
4462 |
|
// (Note the four-character margin) |
4463 |
|
readBuffer = new char[READ_BUFFER_MAX + 4]; |
4464 |
|
readBufferPos = 0; |
4465 |
|
readBufferLength = 0; |
4466 |
|
readBufferOverflow = -1; |
4467 |
|
is = null; |
4468 |
|
line = 1; |
4469 |
|
column = 0; |
4470 |
|
currentByteCount = 0; |
4471 |
|
|
4472 |
|
// If there's an explicit character stream, just |
4473 |
|
// ignore encoding declarations. |
4474 |
|
if (reader != null) |
4475 |
|
{ |
4476 |
|
sourceType = INPUT_READER; |
4477 |
|
this.reader = reader; |
4478 |
|
tryEncodingDecl(true); |
4479 |
|
return; |
4480 |
|
} |
4481 |
|
|
4482 |
|
// Else we handle the conversion, and need to ensure |
4483 |
|
// it's done right. |
4484 |
|
sourceType = INPUT_STREAM; |
4485 |
|
if (stream != null) |
4486 |
|
{ |
4487 |
|
is = stream; |
4488 |
|
} |
4489 |
|
else |
4490 |
|
{ |
4491 |
|
// We have to open our own stream to the URL. |
4492 |
|
URL url = new URL(systemId); |
4493 |
|
|
4494 |
|
externalEntity = url.openConnection(); |
4495 |
|
externalEntity.connect(); |
4496 |
|
is = externalEntity.getInputStream(); |
4497 |
|
} |
4498 |
|
|
4499 |
|
// If we get to here, there must be |
4500 |
|
// an InputStream available. |
4501 |
|
if (!is.markSupported()) |
4502 |
|
{ |
4503 |
|
is = new BufferedInputStream(is); |
4504 |
|
} |
4505 |
|
|
4506 |
/** |
// Get any external encoding label. |
4507 |
* Push, or skip, a new external input source. |
if (encoding == null && externalEntity != null) |
4508 |
* The source will be some kind of parsed entity, such as a PE |
{ |
4509 |
* (including the external DTD subset) or content for the body. |
// External labels can be untrustworthy; filesystems in |
4510 |
* |
// particular often have the wrong default for content |
4511 |
* @param url The java.net.URL object for the entity. |
// that wasn't locally originated. Those we autodetect. |
4512 |
* @see SAXDriver#resolveEntity |
if (!"file".equals(externalEntity.getURL().getProtocol())) |
4513 |
* @see #pushString |
{ |
4514 |
* @see #sourceType |
int temp; |
4515 |
* @see #pushInput |
|
4516 |
* @see #detectEncoding |
// application/xml;charset=something;otherAttr=... |
4517 |
* @see #sourceType |
// ... with many variants on 'something' |
4518 |
* @see #readBuffer |
encoding = externalEntity.getContentType(); |
4519 |
*/ |
|
4520 |
private void pushURL ( |
// MHK code (fix for Saxon 5.5.1/007): |
4521 |
boolean isPE, |
// protect against encoding==null |
4522 |
String ename, |
if (encoding == null) |
4523 |
String ids [], // public, system, baseURI |
{ |
4524 |
Reader reader, |
temp = -1; |
4525 |
InputStream stream, |
} |
4526 |
String encoding, |
else |
4527 |
boolean doResolve |
{ |
4528 |
) throws SAXException, IOException |
temp = encoding.indexOf("charset"); |
4529 |
{ |
} |
4530 |
boolean ignoreEncoding; |
|
4531 |
String systemId; |
// RFC 2376 sez MIME text defaults to ASCII, but since the |
4532 |
InputSource source; |
// JDK will create a MIME type out of thin air, we always |
4533 |
|
// autodetect when there's no explicit charset attribute. |
4534 |
if (!isPE) |
if (temp < 0) |
4535 |
dataBufferFlush (); |
{ |
4536 |
|
encoding = null; // autodetect |
4537 |
scratch.setPublicId (ids [0]); |
} |
4538 |
scratch.setSystemId (ids [1]); |
else |
4539 |
|
{ |
4540 |
// See if we should skip or substitute the entity. |
// only this one attribute |
4541 |
// If we're not skipping, resolving reports startEntity() |
if ((temp = encoding.indexOf(';')) > 0) |
4542 |
// and updates the (handler's) stack of URIs. |
{ |
4543 |
if (doResolve) { |
encoding = encoding.substring(0, temp); |
4544 |
// assert (stream == null && reader == null && encoding == null) |
} |
4545 |
source = handler.resolveEntity (isPE, ename, scratch, ids [2]); |
|
4546 |
if (source == null) { |
if ((temp = encoding.indexOf('=', temp + 7)) > 0) |
4547 |
handler.warn ("skipping entity: " + ename); |
{ |
4548 |
handler.skippedEntity (ename); |
encoding = encoding.substring(temp + 1); |
4549 |
if (isPE) |
|
4550 |
skippedPE = true; |
// attributes can have comment fields (RFC 822) |
4551 |
return; |
if ((temp = encoding.indexOf('(')) > 0) |
4552 |
} |
{ |
4553 |
|
encoding = encoding.substring(0, temp); |
4554 |
// we might be using alternate IDs/encoding |
} |
4555 |
systemId = source.getSystemId (); |
// ... and values may be quoted |
4556 |
// The following warning and setting systemId was deleted bcause |
if ((temp = encoding.indexOf('"')) > 0) |
4557 |
// the application has the option of not setting systemId |
{ |
4558 |
// provided that it has set the characte/byte stream. |
encoding = |
4559 |
/* |
encoding.substring(temp + 1, |
4560 |
if (systemId == null) { |
encoding.indexOf('"', temp + 2)); |
4561 |
handler.warn ("missing system ID, using " + ids [1]); |
} |
4562 |
systemId = ids [1]; |
encoding.trim(); |
4563 |
} |
} |
4564 |
*/ |
else |
4565 |
} else { |
{ |
4566 |
// "[document]", or "[dtd]" via getExternalSubset() |
handler.warn("ignoring illegal MIME attribute: " |
4567 |
scratch.setCharacterStream (reader); |
+ encoding); |
4568 |
scratch.setByteStream (stream); |
encoding = null; |
4569 |
scratch.setEncoding (encoding); |
} |
4570 |
source = scratch; |
} |
4571 |
systemId = ids [1]; |
} |
4572 |
if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { |
} |
4573 |
handler.startExternalEntity (ename, systemId, |
|
4574 |
"[document]" == ename); |
// if we got an external encoding label, use it ... |
4575 |
} else { |
if (encoding != null) |
4576 |
handler.startExternalEntity (ename, systemId, |
{ |
4577 |
"[document]".equals(ename)); |
this.encoding = ENCODING_EXTERNAL; |
4578 |
} |
setupDecoding(encoding); |
4579 |
} |
ignoreEncoding = true; |
4580 |
|
|
4581 |
// we may have been given I/O streams directly |
// ... else autodetect from first bytes. |
4582 |
if (source.getCharacterStream () != null) { |
} |
4583 |
if (source.getByteStream () != null) |
else |
4584 |
error ("InputSource has two streams!"); |
{ |
4585 |
reader = source.getCharacterStream (); |
detectEncoding(); |
4586 |
} else if (source.getByteStream () != null) { |
ignoreEncoding = false; |
4587 |
encoding = source.getEncoding (); |
} |
|
if (encoding == null) |
|
|
stream = source.getByteStream (); |
|
|
else try { |
|
|
reader = new InputStreamReader ( |
|
|
source.getByteStream (), |
|
|
encoding); |
|
|
} catch (IOException e) { |
|
|
stream = source.getByteStream (); |
|
|
} |
|
|
} else if (systemId == null) |
|
|
error ("InputSource has no URI!"); |
|
|
scratch.setCharacterStream (null); |
|
|
scratch.setByteStream (null); |
|
|
scratch.setEncoding (null); |
|
|
|
|
|
// Push the existing status. |
|
|
pushInput (ename); |
|
|
|
|
|
// Create a new read buffer. |
|
|
// (Note the four-character margin) |
|
|
readBuffer = new char [READ_BUFFER_MAX + 4]; |
|
|
readBufferPos = 0; |
|
|
readBufferLength = 0; |
|
|
readBufferOverflow = -1; |
|
|
is = null; |
|
|
line = 1; |
|
|
column = 0; |
|
|
currentByteCount = 0; |
|
|
|
|
|
// If there's an explicit character stream, just |
|
|
// ignore encoding declarations. |
|
|
if (reader != null) { |
|
|
sourceType = INPUT_READER; |
|
|
this.reader = reader; |
|
|
tryEncodingDecl (true); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Else we handle the conversion, and need to ensure |
|
|
// it's done right. |
|
|
sourceType = INPUT_STREAM; |
|
|
if (stream != null) { |
|
|
is = stream; |
|
|
} else { |
|
|
// We have to open our own stream to the URL. |
|
|
URL url = new URL (systemId); |
|
|
|
|
|
externalEntity = url.openConnection (); |
|
|
externalEntity.connect (); |
|
|
is = externalEntity.getInputStream (); |
|
|
} |
|
|
|
|
|
// If we get to here, there must be |
|
|
// an InputStream available. |
|
|
if (!is.markSupported ()) { |
|
|
is = new BufferedInputStream (is); |
|
|
} |
|
|
|
|
|
// Get any external encoding label. |
|
|
if (encoding == null && externalEntity != null) { |
|
|
// External labels can be untrustworthy; filesystems in |
|
|
// particular often have the wrong default for content |
|
|
// that wasn't locally originated. Those we autodetect. |
|
|
if (!"file".equals (externalEntity.getURL ().getProtocol ())) { |
|
|
int temp; |
|
|
|
|
|
// application/xml;charset=something;otherAttr=... |
|
|
// ... with many variants on 'something' |
|
|
encoding = externalEntity.getContentType (); |
|
|
|
|
|
// MHK code (fix for Saxon 5.5.1/007): |
|
|
// protect against encoding==null |
|
|
if (encoding==null) { |
|
|
temp = -1; |
|
|
} else { |
|
|
temp = encoding.indexOf ("charset"); |
|
|
} |
|
|
|
|
|
// RFC 2376 sez MIME text defaults to ASCII, but since the |
|
|
// JDK will create a MIME type out of thin air, we always |
|
|
// autodetect when there's no explicit charset attribute. |
|
|
if (temp < 0) |
|
|
encoding = null; // autodetect |
|
|
else { |
|
|
// only this one attribute |
|
|
if ((temp = encoding.indexOf (';')) > 0) |
|
|
encoding = encoding.substring (0, temp); |
|
|
|
|
|
if ((temp = encoding.indexOf ('=', temp + 7)) > 0) { |
|
|
encoding = encoding.substring (temp + 1); |
|
|
|
|
|
// attributes can have comment fields (RFC 822) |
|
|
if ((temp = encoding.indexOf ('(')) > 0) |
|
|
encoding = encoding.substring (0, temp); |
|
|
// ... and values may be quoted |
|
|
if ((temp = encoding.indexOf ('"')) > 0) |
|
|
encoding = encoding.substring (temp + 1, |
|
|
encoding.indexOf ('"', temp + 2)); |
|
|
encoding.trim (); |
|
|
} else { |
|
|
handler.warn ("ignoring illegal MIME attribute: " |
|
|
+ encoding); |
|
|
encoding = null; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// if we got an external encoding label, use it ... |
|
|
if (encoding != null) { |
|
|
this.encoding = ENCODING_EXTERNAL; |
|
|
setupDecoding (encoding); |
|
|
ignoreEncoding = true; |
|
|
|
|
|
// ... else autodetect from first bytes. |
|
|
} else { |
|
|
detectEncoding (); |
|
|
ignoreEncoding = false; |
|
|
} |
|
|
|
|
|
// Read any XML or text declaration. |
|
|
// If we autodetected, it may tell us the "real" encoding. |
|
|
try { |
|
|
tryEncodingDecl (ignoreEncoding); |
|
|
} catch (UnsupportedEncodingException x) { |
|
|
encoding = x.getMessage (); |
|
|
|
|
|
// if we don't handle the declared encoding, |
|
|
// try letting a JVM InputStreamReader do it |
|
|
try { |
|
|
if (sourceType != INPUT_STREAM) |
|
|
throw x; |
|
|
|
|
|
is.reset (); |
|
|
readBufferPos = 0; |
|
|
readBufferLength = 0; |
|
|
readBufferOverflow = -1; |
|
|
line = 1; |
|
|
currentByteCount = column = 0; |
|
|
|
|
|
sourceType = INPUT_READER; |
|
|
this.reader = new InputStreamReader (is, encoding); |
|
|
is = null; |
|
|
|
|
|
tryEncodingDecl (true); |
|
|
|
|
|
} catch (IOException e) { |
|
|
error ("unsupported text encoding", |
|
|
encoding, |
|
|
null); |
|
|
} |
|
|
} |
|
|
} |
|
4588 |
|
|
4589 |
|
// Read any XML or text declaration. |
4590 |
|
// If we autodetected, it may tell us the "real" encoding. |
4591 |
|
try |
4592 |
|
{ |
4593 |
|
tryEncodingDecl(ignoreEncoding); |
4594 |
|
} |
4595 |
|
catch (UnsupportedEncodingException x) |
4596 |
|
{ |
4597 |
|
encoding = x.getMessage(); |
4598 |
|
|
4599 |
|
// if we don't handle the declared encoding, |
4600 |
|
// try letting a JVM InputStreamReader do it |
4601 |
|
try |
4602 |
|
{ |
4603 |
|
if (sourceType != INPUT_STREAM) |
4604 |
|
{ |
4605 |
|
throw x; |
4606 |
|
} |
4607 |
|
|
4608 |
|
is.reset(); |
4609 |
|
readBufferPos = 0; |
4610 |
|
readBufferLength = 0; |
4611 |
|
readBufferOverflow = -1; |
4612 |
|
line = 1; |
4613 |
|
currentByteCount = column = 0; |
4614 |
|
|
4615 |
|
sourceType = INPUT_READER; |
4616 |
|
this.reader = new InputStreamReader(is, encoding); |
4617 |
|
is = null; |
4618 |
|
|
4619 |
|
tryEncodingDecl(true); |
4620 |
|
|
4621 |
|
} |
4622 |
|
catch (IOException e) |
4623 |
|
{ |
4624 |
|
error("unsupported text encoding", |
4625 |
|
encoding, |
4626 |
|
null); |
4627 |
|
} |
4628 |
|
} |
4629 |
|
} |
4630 |
|
|
4631 |
/** |
/** |
4632 |
* Check for an encoding declaration. This is the second part of the |
* Check for an encoding declaration. This is the second part of the |
4633 |
* XML encoding autodetection algorithm, relying on detectEncoding to |
* XML encoding autodetection algorithm, relying on detectEncoding to |
4634 |
* get to the point that this part can read any encoding declaration |
* get to the point that this part can read any encoding declaration |
4635 |
* in the document (using only US-ASCII characters). |
* in the document (using only US-ASCII characters). |
4636 |
* |
* |
4637 |
* <p> Because this part starts to fill parser buffers with this data, |
* <p> Because this part starts to fill parser buffers with this data, |
4638 |
* it's tricky to setup a reader so that Java's built-in decoders can be |
* it's tricky to setup a reader so that Java's built-in decoders can be |
4639 |
* used for the character encodings that aren't built in to this parser |
* used for the character encodings that aren't built in to this parser |
4640 |
* (such as EUC-JP, KOI8-R, Big5, etc). |
* (such as EUC-JP, KOI8-R, Big5, etc). |
4641 |
* |
* |
4642 |
* @return any encoding in the declaration, uppercased; or null |
* @return any encoding in the declaration, uppercased; or null |
4643 |
* @see detectEncoding |
* @see detectEncoding |
4644 |
*/ |
*/ |
4645 |
private String tryEncodingDecl (boolean ignoreEncoding) |
private String tryEncodingDecl(boolean ignoreEncoding) |
4646 |
throws SAXException, IOException |
throws SAXException, IOException |
4647 |
{ |
{ |
4648 |
// Read the XML/text declaration. |
// Read the XML/text declaration. |
4649 |
if (tryRead ("<?xml")) { |
if (tryRead("<?xml")) |
4650 |
if (tryWhitespace ()) { |
{ |
4651 |
if (inputStack.size () > 0) { |
if (tryWhitespace()) |
4652 |
return parseTextDecl (ignoreEncoding); |
{ |
4653 |
} else { |
if (inputStack.size() > 0) |
4654 |
return parseXMLDecl (ignoreEncoding); |
{ |
4655 |
} |
return parseTextDecl(ignoreEncoding); |
4656 |
} else { |
} |
4657 |
// <?xml-stylesheet ...?> or similar |
else |
4658 |
unread ('l'); |
{ |
4659 |
unread ('m'); |
return parseXMLDecl(ignoreEncoding); |
4660 |
unread ('x'); |
} |
4661 |
unread ('?'); |
} |
4662 |
unread ('<'); |
else |
4663 |
} |
{ |
4664 |
} |
// <?xml-stylesheet ...?> or similar |
4665 |
return null; |
unread('l'); |
4666 |
} |
unread('m'); |
4667 |
|
unread('x'); |
4668 |
|
unread('?'); |
4669 |
|
unread('<'); |
4670 |
|
} |
4671 |
|
} |
4672 |
|
return null; |
4673 |
|
} |
4674 |
|
|
4675 |
/** |
/** |
4676 |
* Attempt to detect the encoding of an entity. |
* Attempt to detect the encoding of an entity. |
4677 |
* <p>The trick here (as suggested in the XML standard) is that |
* <p>The trick here (as suggested in the XML standard) is that |
4678 |
* any entity not in UTF-8, or in UCS-2 with a byte-order mark, |
* any entity not in UTF-8, or in UCS-2 with a byte-order mark, |
4679 |
* <b>must</b> begin with an XML declaration or an encoding |
* <b>must</b> begin with an XML declaration or an encoding |
4680 |
* declaration; we simply have to look for "<?xml" in various |
* declaration; we simply have to look for "<?xml" in various |
4681 |
* encodings. |
* encodings. |
4682 |
* <p>This method has no way to distinguish among 8-bit encodings. |
* <p>This method has no way to distinguish among 8-bit encodings. |
4683 |
* Instead, it sets up for UTF-8, then (possibly) revises its assumption |
* Instead, it sets up for UTF-8, then (possibly) revises its assumption |
4684 |
* later in setupDecoding (). Any ASCII-derived 8-bit encoding |
* later in setupDecoding (). Any ASCII-derived 8-bit encoding |
4685 |
* should work, but most will be rejected later by setupDecoding (). |
* should work, but most will be rejected later by setupDecoding (). |
4686 |
* @see #tryEncoding (byte[], byte, byte, byte, byte) |
* @see #tryEncoding (byte[], byte, byte, byte, byte) |
4687 |
* @see #tryEncoding (byte[], byte, byte) |
* @see #tryEncoding (byte[], byte, byte) |
4688 |
* @see #setupDecoding |
* @see #setupDecoding |
4689 |
*/ |
*/ |
4690 |
private void detectEncoding () |
private void detectEncoding() |
4691 |
throws SAXException, IOException |
throws SAXException, IOException |
4692 |
{ |
{ |
4693 |
byte signature[] = new byte [4]; |
byte[] signature = new byte[4]; |
4694 |
|
|
4695 |
// Read the first four bytes for |
// Read the first four bytes for |
4696 |
// autodetection. |
// autodetection. |
4697 |
is.mark (4); |
is.mark(4); |
4698 |
is.read (signature); |
is.read(signature); |
4699 |
is.reset (); |
is.reset(); |
4700 |
|
|
4701 |
// |
// |
4702 |
// FIRST: four byte encodings (who uses these?) |
// FIRST: four byte encodings (who uses these?) |
4703 |
// |
// |
4704 |
if (tryEncoding (signature, (byte) 0x00, (byte) 0x00, |
if (tryEncoding(signature, (byte) 0x00, (byte) 0x00, |
4705 |
(byte) 0x00, (byte) 0x3c)) { |
(byte) 0x00, (byte) 0x3c)) |
4706 |
// UCS-4 must begin with "<?xml" |
{ |
4707 |
// 0x00 0x00 0x00 0x3c: UCS-4, big-endian (1234) |
// UCS-4 must begin with "<?xml" |
4708 |
// "UTF-32BE" |
// 0x00 0x00 0x00 0x3c: UCS-4, big-endian (1234) |
4709 |
encoding = ENCODING_UCS_4_1234; |
// "UTF-32BE" |
4710 |
|
encoding = ENCODING_UCS_4_1234; |
4711 |
} else if (tryEncoding (signature, (byte) 0x3c, (byte) 0x00, |
} |
4712 |
(byte) 0x00, (byte) 0x00)) { |
else if (tryEncoding(signature, (byte) 0x3c, (byte) 0x00, |
4713 |
// 0x3c 0x00 0x00 0x00: UCS-4, little-endian (4321) |
(byte) 0x00, (byte) 0x00)) |
4714 |
// "UTF-32LE" |
{ |
4715 |
encoding = ENCODING_UCS_4_4321; |
// 0x3c 0x00 0x00 0x00: UCS-4, little-endian (4321) |
4716 |
|
// "UTF-32LE" |
4717 |
} else if (tryEncoding (signature, (byte) 0x00, (byte) 0x00, |
encoding = ENCODING_UCS_4_4321; |
4718 |
(byte) 0x3c, (byte) 0x00)) { |
} |
4719 |
// 0x00 0x00 0x3c 0x00: UCS-4, unusual (2143) |
else if (tryEncoding(signature, (byte) 0x00, (byte) 0x00, |
4720 |
encoding = ENCODING_UCS_4_2143; |
(byte) 0x3c, (byte) 0x00)) |
4721 |
|
{ |
4722 |
} else if (tryEncoding (signature, (byte) 0x00, (byte) 0x3c, |
// 0x00 0x00 0x3c 0x00: UCS-4, unusual (2143) |
4723 |
(byte) 0x00, (byte) 0x00)) { |
encoding = ENCODING_UCS_4_2143; |
4724 |
// 0x00 0x3c 0x00 0x00: UCS-4, unusual (3421) |
} |
4725 |
encoding = ENCODING_UCS_4_3412; |
else if (tryEncoding(signature, (byte) 0x00, (byte) 0x3c, |
4726 |
|
(byte) 0x00, (byte) 0x00)) |
4727 |
// 00 00 fe ff UCS_4_1234 (with BOM) |
{ |
4728 |
// ff fe 00 00 UCS_4_4321 (with BOM) |
// 0x00 0x3c 0x00 0x00: UCS-4, unusual (3421) |
4729 |
} |
encoding = ENCODING_UCS_4_3412; |
|
|
|
|
// |
|
|
// SECOND: two byte encodings |
|
|
// note ... with 1/14/2000 errata the XML spec identifies some |
|
|
// more "broken UTF-16" autodetection cases, with no XML decl, |
|
|
// which we don't handle here (that's legal too). |
|
|
// |
|
|
else if (tryEncoding (signature, (byte) 0xfe, (byte) 0xff)) { |
|
|
// UCS-2 with a byte-order marker. (UTF-16) |
|
|
// 0xfe 0xff: UCS-2, big-endian (12) |
|
|
encoding = ENCODING_UCS_2_12; |
|
|
is.read (); is.read (); |
|
|
|
|
|
} else if (tryEncoding (signature, (byte) 0xff, (byte) 0xfe)) { |
|
|
// UCS-2 with a byte-order marker. (UTF-16) |
|
|
// 0xff 0xfe: UCS-2, little-endian (21) |
|
|
encoding = ENCODING_UCS_2_21; |
|
|
is.read (); is.read (); |
|
|
|
|
|
} else if (tryEncoding (signature, (byte) 0x00, (byte) 0x3c, |
|
|
(byte) 0x00, (byte) 0x3f)) { |
|
|
// UTF-16BE (otherwise, malformed UTF-16) |
|
|
// 0x00 0x3c 0x00 0x3f: UCS-2, big-endian, no byte-order mark |
|
|
encoding = ENCODING_UCS_2_12; |
|
|
error ("no byte-order mark for UCS-2 entity"); |
|
|
|
|
|
} else if (tryEncoding (signature, (byte) 0x3c, (byte) 0x00, |
|
|
(byte) 0x3f, (byte) 0x00)) { |
|
|
// UTF-16LE (otherwise, malformed UTF-16) |
|
|
// 0x3c 0x00 0x3f 0x00: UCS-2, little-endian, no byte-order mark |
|
|
encoding = ENCODING_UCS_2_21; |
|
|
error ("no byte-order mark for UCS-2 entity"); |
|
|
} |
|
|
|
|
|
// |
|
|
// THIRD: ASCII-derived encodings, fixed and variable lengths |
|
|
// |
|
|
else if (tryEncoding (signature, (byte) 0x3c, (byte) 0x3f, |
|
|
(byte) 0x78, (byte) 0x6d)) { |
|
|
// ASCII derived |
|
|
// 0x3c 0x3f 0x78 0x6d: UTF-8 or other 8-bit markup (read ENCODING) |
|
|
encoding = ENCODING_UTF_8; |
|
|
prefetchASCIIEncodingDecl (); |
|
|
|
|
|
} else if (signature [0] == (byte) 0xef |
|
|
&& signature [1] == (byte) 0xbb |
|
|
&& signature [2] == (byte) 0xbf) { |
|
|
// 0xef 0xbb 0xbf: UTF-8 BOM (not part of document text) |
|
|
// this un-needed notion slipped into XML 2nd ed through a |
|
|
// "non-normative" erratum; now required by MSFT and UDDI, |
|
|
// and E22 made it normative. |
|
|
encoding = ENCODING_UTF_8; |
|
|
is.read (); is.read (); is.read (); |
|
|
|
|
|
} else { |
|
|
// 4c 6f a7 94 ... we don't understand EBCDIC flavors |
|
|
// ... but we COULD at least kick in some fixed code page |
|
|
|
|
|
// (default) UTF-8 without encoding/XML declaration |
|
|
encoding = ENCODING_UTF_8; |
|
|
} |
|
|
} |
|
4730 |
|
|
4731 |
|
// 00 00 fe ff UCS_4_1234 (with BOM) |
4732 |
|
// ff fe 00 00 UCS_4_4321 (with BOM) |
4733 |
|
} |
4734 |
|
|
4735 |
/** |
// |
4736 |
* Check for a four-byte signature. |
// SECOND: two byte encodings |
4737 |
* <p>Utility routine for detectEncoding (). |
// note ... with 1/14/2000 errata the XML spec identifies some |
4738 |
* <p>Always looks for some part of "<?XML" in a specific encoding. |
// more "broken UTF-16" autodetection cases, with no XML decl, |
4739 |
* @param sig The first four bytes read. |
// which we don't handle here (that's legal too). |
4740 |
* @param b1 The first byte of the signature |
// |
4741 |
* @param b2 The second byte of the signature |
else if (tryEncoding(signature, (byte) 0xfe, (byte) 0xff)) |
4742 |
* @param b3 The third byte of the signature |
{ |
4743 |
* @param b4 The fourth byte of the signature |
// UCS-2 with a byte-order marker. (UTF-16) |
4744 |
* @see #detectEncoding |
// 0xfe 0xff: UCS-2, big-endian (12) |
4745 |
*/ |
encoding = ENCODING_UCS_2_12; |
4746 |
private static boolean tryEncoding ( |
is.read(); is.read(); |
4747 |
byte sig[], byte b1, byte b2, byte b3, byte b4) |
} |
4748 |
{ |
else if (tryEncoding(signature, (byte) 0xff, (byte) 0xfe)) |
4749 |
return (sig [0] == b1 && sig [1] == b2 |
{ |
4750 |
&& sig [2] == b3 && sig [3] == b4); |
// UCS-2 with a byte-order marker. (UTF-16) |
4751 |
} |
// 0xff 0xfe: UCS-2, little-endian (21) |
4752 |
|
encoding = ENCODING_UCS_2_21; |
4753 |
|
is.read(); is.read(); |
4754 |
|
} |
4755 |
|
else if (tryEncoding(signature, (byte) 0x00, (byte) 0x3c, |
4756 |
|
(byte) 0x00, (byte) 0x3f)) |
4757 |
|
{ |
4758 |
|
// UTF-16BE (otherwise, malformed UTF-16) |
4759 |
|
// 0x00 0x3c 0x00 0x3f: UCS-2, big-endian, no byte-order mark |
4760 |
|
encoding = ENCODING_UCS_2_12; |
4761 |
|
error("no byte-order mark for UCS-2 entity"); |
4762 |
|
} |
4763 |
|
else if (tryEncoding(signature, (byte) 0x3c, (byte) 0x00, |
4764 |
|
(byte) 0x3f, (byte) 0x00)) |
4765 |
|
{ |
4766 |
|
// UTF-16LE (otherwise, malformed UTF-16) |
4767 |
|
// 0x3c 0x00 0x3f 0x00: UCS-2, little-endian, no byte-order mark |
4768 |
|
encoding = ENCODING_UCS_2_21; |
4769 |
|
error("no byte-order mark for UCS-2 entity"); |
4770 |
|
} |
4771 |
|
|
4772 |
|
// |
4773 |
|
// THIRD: ASCII-derived encodings, fixed and variable lengths |
4774 |
|
// |
4775 |
|
else if (tryEncoding(signature, (byte) 0x3c, (byte) 0x3f, |
4776 |
|
(byte) 0x78, (byte) 0x6d)) |
4777 |
|
{ |
4778 |
|
// ASCII derived |
4779 |
|
// 0x3c 0x3f 0x78 0x6d: UTF-8 or other 8-bit markup (read ENCODING) |
4780 |
|
encoding = ENCODING_UTF_8; |
4781 |
|
prefetchASCIIEncodingDecl(); |
4782 |
|
} |
4783 |
|
else if (signature[0] == (byte) 0xef |
4784 |
|
&& signature[1] == (byte) 0xbb |
4785 |
|
&& signature[2] == (byte) 0xbf) |
4786 |
|
{ |
4787 |
|
// 0xef 0xbb 0xbf: UTF-8 BOM (not part of document text) |
4788 |
|
// this un-needed notion slipped into XML 2nd ed through a |
4789 |
|
// "non-normative" erratum; now required by MSFT and UDDI, |
4790 |
|
// and E22 made it normative. |
4791 |
|
encoding = ENCODING_UTF_8; |
4792 |
|
is.read(); is.read(); is.read(); |
4793 |
|
} |
4794 |
|
else |
4795 |
|
{ |
4796 |
|
// 4c 6f a7 94 ... we don't understand EBCDIC flavors |
4797 |
|
// ... but we COULD at least kick in some fixed code page |
4798 |
|
|
4799 |
|
// (default) UTF-8 without encoding/XML declaration |
4800 |
|
encoding = ENCODING_UTF_8; |
4801 |
|
} |
4802 |
|
} |
4803 |
|
|
4804 |
/** |
/** |
4805 |
* Check for a two-byte signature. |
* Check for a four-byte signature. |
4806 |
* <p>Looks for a UCS-2 byte-order mark. |
* <p>Utility routine for detectEncoding (). |
4807 |
* <p>Utility routine for detectEncoding (). |
* <p>Always looks for some part of "<?XML" in a specific encoding. |
4808 |
* @param sig The first four bytes read. |
* @param sig The first four bytes read. |
4809 |
* @param b1 The first byte of the signature |
* @param b1 The first byte of the signature |
4810 |
* @param b2 The second byte of the signature |
* @param b2 The second byte of the signature |
4811 |
* @see #detectEncoding |
* @param b3 The third byte of the signature |
4812 |
*/ |
* @param b4 The fourth byte of the signature |
4813 |
private static boolean tryEncoding (byte sig[], byte b1, byte b2) |
* @see #detectEncoding |
4814 |
{ |
*/ |
4815 |
return ((sig [0] == b1) && (sig [1] == b2)); |
private static boolean tryEncoding(byte[] sig, byte b1, byte b2, |
4816 |
} |
byte b3, byte b4) |
4817 |
|
{ |
4818 |
|
return (sig[0] == b1 && sig[1] == b2 |
4819 |
|
&& sig[2] == b3 && sig[3] == b4); |
4820 |
|
} |
4821 |
|
|
4822 |
|
/** |
4823 |
|
* Check for a two-byte signature. |
4824 |
|
* <p>Looks for a UCS-2 byte-order mark. |
4825 |
|
* <p>Utility routine for detectEncoding (). |
4826 |
|
* @param sig The first four bytes read. |
4827 |
|
* @param b1 The first byte of the signature |
4828 |
|
* @param b2 The second byte of the signature |
4829 |
|
* @see #detectEncoding |
4830 |
|
*/ |
4831 |
|
private static boolean tryEncoding(byte[] sig, byte b1, byte b2) |
4832 |
|
{ |
4833 |
|
return ((sig[0] == b1) && (sig[1] == b2)); |
4834 |
|
} |
4835 |
|
|
4836 |
/** |
/** |
4837 |
* This method pushes a string back onto input. |
* This method pushes a string back onto input. |
4838 |
* <p>It is useful either as the expansion of an internal entity, |
* <p>It is useful either as the expansion of an internal entity, |
4839 |
* or for backtracking during the parse. |
* or for backtracking during the parse. |
4840 |
* <p>Call pushCharArray () to do the actual work. |
* <p>Call pushCharArray () to do the actual work. |
4841 |
* @param s The string to push back onto input. |
* @param s The string to push back onto input. |
4842 |
* @see #pushCharArray |
* @see #pushCharArray |
4843 |
*/ |
*/ |
4844 |
private void pushString (String ename, String s) |
private void pushString(String ename, String s) |
4845 |
throws SAXException |
throws SAXException |
4846 |
{ |
{ |
4847 |
char ch[] = s.toCharArray (); |
char[] ch = s.toCharArray(); |
4848 |
pushCharArray (ename, ch, 0, ch.length); |
pushCharArray(ename, ch, 0, ch.length); |
4849 |
} |
} |
|
|
|
4850 |
|
|
4851 |
/** |
/** |
4852 |
* Push a new internal input source. |
* Push a new internal input source. |
4853 |
* <p>This method is useful for expanding an internal entity, |
* <p>This method is useful for expanding an internal entity, |
4854 |
* or for unreading a string of characters. It creates a new |
* or for unreading a string of characters. It creates a new |
4855 |
* readBuffer containing the characters in the array, instead |
* readBuffer containing the characters in the array, instead |
4856 |
* of characters converted from an input byte stream. |
* of characters converted from an input byte stream. |
4857 |
* @param ch The char array to push. |
* @param ch The char array to push. |
4858 |
* @see #pushString |
* @see #pushString |
4859 |
* @see #pushURL |
* @see #pushURL |
4860 |
* @see #readBuffer |
* @see #readBuffer |
4861 |
* @see #sourceType |
* @see #sourceType |
4862 |
* @see #pushInput |
* @see #pushInput |
4863 |
*/ |
*/ |
4864 |
private void pushCharArray (String ename, char ch[], int start, int length) |
private void pushCharArray(String ename, char[] ch, int start, int length) |
4865 |
throws SAXException |
throws SAXException |
4866 |
{ |
{ |
4867 |
// Push the existing status |
// Push the existing status |
4868 |
pushInput (ename); |
pushInput(ename); |
4869 |
if (ename != null && doReport) { |
if (ename != null && doReport) |
4870 |
dataBufferFlush (); |
{ |
4871 |
handler.startInternalEntity (ename); |
dataBufferFlush(); |
4872 |
} |
handler.startInternalEntity(ename); |
4873 |
sourceType = INPUT_INTERNAL; |
} |
4874 |
readBuffer = ch; |
sourceType = INPUT_INTERNAL; |
4875 |
readBufferPos = start; |
readBuffer = ch; |
4876 |
readBufferLength = length; |
readBufferPos = start; |
4877 |
readBufferOverflow = -1; |
readBufferLength = length; |
4878 |
} |
readBufferOverflow = -1; |
4879 |
|
} |
4880 |
|
|
4881 |
/** |
/** |
4882 |
* Save the current input source onto the stack. |
* Save the current input source onto the stack. |
4883 |
* <p>This method saves all of the global variables associated with |
* <p>This method saves all of the global variables associated with |
4884 |
* the current input source, so that they can be restored when a new |
* the current input source, so that they can be restored when a new |
4885 |
* input source has finished. It also tests for entity recursion. |
* input source has finished. It also tests for entity recursion. |
4886 |
* <p>The method saves the following global variables onto a stack |
* <p>The method saves the following global variables onto a stack |
4887 |
* using a fixed-length array: |
* using a fixed-length array: |
4888 |
* <ol> |
* <ol> |
4889 |
* <li>sourceType |
* <li>sourceType |
4890 |
* <li>externalEntity |
* <li>externalEntity |
4891 |
* <li>readBuffer |
* <li>readBuffer |
4892 |
* <li>readBufferPos |
* <li>readBufferPos |
4893 |
* <li>readBufferLength |
* <li>readBufferLength |
4894 |
* <li>line |
* <li>line |
4895 |
* <li>encoding |
* <li>encoding |
4896 |
* </ol> |
* </ol> |
4897 |
* @param ename The name of the entity (if any) causing the new input. |
* @param ename The name of the entity (if any) causing the new input. |
4898 |
* @see #popInput |
* @see #popInput |
4899 |
* @see #sourceType |
* @see #sourceType |
4900 |
* @see #externalEntity |
* @see #externalEntity |
4901 |
* @see #readBuffer |
* @see #readBuffer |
4902 |
* @see #readBufferPos |
* @see #readBufferPos |
4903 |
* @see #readBufferLength |
* @see #readBufferLength |
4904 |
* @see #line |
* @see #line |
4905 |
* @see #encoding |
* @see #encoding |
4906 |
*/ |
*/ |
4907 |
private void pushInput (String ename) |
private void pushInput(String ename) |
4908 |
throws SAXException |
throws SAXException |
4909 |
{ |
{ |
4910 |
// Check for entity recursion. |
// Check for entity recursion. |
4911 |
if (ename != null) { |
if (ename != null) |
4912 |
Enumeration entities = entityStack.elements (); |
{ |
4913 |
while (entities.hasMoreElements ()) { |
Iterator entities = entityStack.iterator(); |
4914 |
String e = (String) entities.nextElement (); |
while (entities.hasNext()) |
4915 |
if (e != null && e == ename) { |
{ |
4916 |
error ("recursive reference to entity", ename, null); |
String e = (String) entities.next(); |
4917 |
} |
if (e != null && e == ename) |
4918 |
} |
{ |
4919 |
} |
error("recursive reference to entity", ename, null); |
4920 |
entityStack.push (ename); |
} |
4921 |
|
} |
4922 |
// Don't bother if there is no current input. |
} |
4923 |
if (sourceType == INPUT_NONE) { |
entityStack.addLast(ename); |
4924 |
return; |
|
4925 |
} |
// Don't bother if there is no current input. |
4926 |
|
if (sourceType == INPUT_NONE) |
4927 |
// Set up a snapshot of the current |
{ |
4928 |
// input source. |
return; |
4929 |
Object input[] = new Object [12]; |
} |
4930 |
|
|
4931 |
input [0] = new Integer (sourceType); |
// Set up a snapshot of the current |
4932 |
input [1] = externalEntity; |
// input source. |
4933 |
input [2] = readBuffer; |
Input input = new Input(); |
4934 |
input [3] = new Integer (readBufferPos); |
|
4935 |
input [4] = new Integer (readBufferLength); |
input.sourceType = sourceType; |
4936 |
input [5] = new Integer (line); |
input.externalEntity = externalEntity; |
4937 |
input [6] = new Integer (encoding); |
input.readBuffer = readBuffer; |
4938 |
input [7] = new Integer (readBufferOverflow); |
input.readBufferPos = readBufferPos; |
4939 |
input [8] = is; |
input.readBufferLength = readBufferLength; |
4940 |
input [9] = new Integer (currentByteCount); |
input.line = line; |
4941 |
input [10] = new Integer (column); |
input.encoding = encoding; |
4942 |
input [11] = reader; |
input.readBufferOverflow = readBufferOverflow; |
4943 |
|
input.is = is; |
4944 |
// Push it onto the stack. |
input.currentByteCount = currentByteCount; |
4945 |
inputStack.push (input); |
input.column = column; |
4946 |
} |
input.reader = reader; |
4947 |
|
|
4948 |
|
// Push it onto the stack. |
4949 |
|
inputStack.addLast(input); |
4950 |
|
} |
4951 |
|
|
4952 |
/** |
/** |
4953 |
* Restore a previous input source. |
* Restore a previous input source. |
4954 |
* <p>This method restores all of the global variables associated with |
* <p>This method restores all of the global variables associated with |
4955 |
* the current input source. |
* the current input source. |
4956 |
* @exception java.io.EOFException |
* @exception java.io.EOFException |
4957 |
* If there are no more entries on the input stack. |
* If there are no more entries on the input stack. |
4958 |
* @see #pushInput |
* @see #pushInput |
4959 |
* @see #sourceType |
* @see #sourceType |
4960 |
* @see #externalEntity |
* @see #externalEntity |
4961 |
* @see #readBuffer |
* @see #readBuffer |
4962 |
* @see #readBufferPos |
* @see #readBufferPos |
4963 |
* @see #readBufferLength |
* @see #readBufferLength |
4964 |
* @see #line |
* @see #line |
4965 |
* @see #encoding |
* @see #encoding |
4966 |
*/ |
*/ |
4967 |
private void popInput () |
private void popInput() |
4968 |
throws SAXException, IOException |
throws SAXException, IOException |
4969 |
{ |
{ |
4970 |
String ename = (String) entityStack.pop (); |
String ename = (String) entityStack.removeLast(); |
|
|
|
|
if (ename != null && doReport) |
|
|
dataBufferFlush (); |
|
|
switch (sourceType) { |
|
|
case INPUT_STREAM: |
|
|
handler.endExternalEntity (ename); |
|
|
is.close (); |
|
|
break; |
|
|
case INPUT_READER: |
|
|
handler.endExternalEntity (ename); |
|
|
reader.close (); |
|
|
break; |
|
|
case INPUT_INTERNAL: |
|
|
if (ename != null && doReport) |
|
|
handler.endInternalEntity (ename); |
|
|
break; |
|
|
} |
|
|
|
|
|
// Throw an EOFException if there |
|
|
// is nothing else to pop. |
|
|
if (inputStack.isEmpty ()) { |
|
|
throw new EOFException ("no more input"); |
|
|
} |
|
|
|
|
|
Object input [] = (Object[]) inputStack.pop (); |
|
|
|
|
|
sourceType = ((Integer) input [0]).intValue (); |
|
|
externalEntity = (URLConnection) input [1]; |
|
|
readBuffer = (char[]) input [2]; |
|
|
readBufferPos = ((Integer) input [3]).intValue (); |
|
|
readBufferLength = ((Integer) input [4]).intValue (); |
|
|
line = ((Integer) input [5]).intValue (); |
|
|
encoding = ((Integer) input [6]).intValue (); |
|
|
readBufferOverflow = ((Integer) input [7]).intValue (); |
|
|
is = (InputStream) input [8]; |
|
|
currentByteCount = ((Integer) input [9]).intValue (); |
|
|
column = ((Integer) input [10]).intValue (); |
|
|
reader = (Reader) input [11]; |
|
|
} |
|
4971 |
|
|
4972 |
|
if (ename != null && doReport) |
4973 |
|
{ |
4974 |
|
dataBufferFlush(); |
4975 |
|
} |
4976 |
|
switch (sourceType) |
4977 |
|
{ |
4978 |
|
case INPUT_STREAM: |
4979 |
|
handler.endExternalEntity(ename); |
4980 |
|
is.close(); |
4981 |
|
break; |
4982 |
|
case INPUT_READER: |
4983 |
|
handler.endExternalEntity(ename); |
4984 |
|
reader.close(); |
4985 |
|
break; |
4986 |
|
case INPUT_INTERNAL: |
4987 |
|
if (ename != null && doReport) |
4988 |
|
{ |
4989 |
|
handler.endInternalEntity(ename); |
4990 |
|
} |
4991 |
|
break; |
4992 |
|
} |
4993 |
|
|
4994 |
/** |
// Throw an EOFException if there |
4995 |
* Return true if we can read the expected character. |
// is nothing else to pop. |
4996 |
* <p>Note that the character will be removed from the input stream |
if (inputStack.isEmpty()) |
4997 |
* on success, but will be put back on failure. Do not attempt to |
{ |
4998 |
* read the character again if the method succeeds. |
throw new EOFException("no more input"); |
4999 |
* @param delim The character that should appear next. For a |
} |
|
* insensitive match, you must supply this in upper-case. |
|
|
* @return true if the character was successfully read, or false if |
|
|
* it was not. |
|
|
* @see #tryRead (String) |
|
|
*/ |
|
|
private boolean tryRead (char delim) |
|
|
throws SAXException, IOException |
|
|
{ |
|
|
char c; |
|
5000 |
|
|
5001 |
// Read the character |
Input input = (Input) inputStack.removeLast(); |
|
c = readCh (); |
|
5002 |
|
|
5003 |
// Test for a match, and push the character |
sourceType = input.sourceType; |
5004 |
// back if the match fails. |
externalEntity = input.externalEntity; |
5005 |
if (c == delim) { |
readBuffer = input.readBuffer; |
5006 |
return true; |
readBufferPos = input.readBufferPos; |
5007 |
} else { |
readBufferLength = input.readBufferLength; |
5008 |
unread (c); |
line = input.line; |
5009 |
return false; |
encoding = input.encoding; |
5010 |
} |
readBufferOverflow = input.readBufferOverflow; |
5011 |
} |
is = input.is; |
5012 |
|
currentByteCount = input.currentByteCount; |
5013 |
|
column = input.column; |
5014 |
|
reader = input.reader; |
5015 |
|
} |
5016 |
|
|
5017 |
|
/** |
5018 |
|
* Return true if we can read the expected character. |
5019 |
|
* <p>Note that the character will be removed from the input stream |
5020 |
|
* on success, but will be put back on failure. Do not attempt to |
5021 |
|
* read the character again if the method succeeds. |
5022 |
|
* @param delim The character that should appear next. For a |
5023 |
|
* insensitive match, you must supply this in upper-case. |
5024 |
|
* @return true if the character was successfully read, or false if |
5025 |
|
* it was not. |
5026 |
|
* @see #tryRead (String) |
5027 |
|
*/ |
5028 |
|
private boolean tryRead(char delim) |
5029 |
|
throws SAXException, IOException |
5030 |
|
{ |
5031 |
|
char c; |
5032 |
|
|
5033 |
|
// Read the character |
5034 |
|
c = readCh(); |
5035 |
|
|
5036 |
|
// Test for a match, and push the character |
5037 |
|
// back if the match fails. |
5038 |
|
if (c == delim) |
5039 |
|
{ |
5040 |
|
return true; |
5041 |
|
} |
5042 |
|
else |
5043 |
|
{ |
5044 |
|
unread(c); |
5045 |
|
return false; |
5046 |
|
} |
5047 |
|
} |
5048 |
|
|
5049 |
/** |
/** |
5050 |
* Return true if we can read the expected string. |
* Return true if we can read the expected string. |
5051 |
* <p>This is simply a convenience method. |
* <p>This is simply a convenience method. |
5052 |
* <p>Note that the string will be removed from the input stream |
* <p>Note that the string will be removed from the input stream |
5053 |
* on success, but will be put back on failure. Do not attempt to |
* on success, but will be put back on failure. Do not attempt to |
5054 |
* read the string again if the method succeeds. |
* read the string again if the method succeeds. |
5055 |
* <p>This method will push back a character rather than an |
* <p>This method will push back a character rather than an |
5056 |
* array whenever possible (probably the majority of cases). |
* array whenever possible (probably the majority of cases). |
5057 |
* @param delim The string that should appear next. |
* @param delim The string that should appear next. |
5058 |
* @return true if the string was successfully read, or false if |
* @return true if the string was successfully read, or false if |
5059 |
* it was not. |
* it was not. |
5060 |
* @see #tryRead (char) |
* @see #tryRead (char) |
5061 |
*/ |
*/ |
5062 |
private boolean tryRead (String delim) |
private boolean tryRead(String delim) |
5063 |
throws SAXException, IOException |
throws SAXException, IOException |
5064 |
{ |
{ |
5065 |
return tryRead (delim.toCharArray ()); |
return tryRead(delim.toCharArray()); |
5066 |
} |
} |
5067 |
|
|
5068 |
private boolean tryRead (char ch []) |
private boolean tryRead(char[] ch) |
5069 |
throws SAXException, IOException |
throws SAXException, IOException |
5070 |
{ |
{ |
5071 |
char c; |
char c; |
|
|
|
|
// Compare the input, character- |
|
|
// by character. |
|
|
|
|
|
for (int i = 0; i < ch.length; i++) { |
|
|
c = readCh (); |
|
|
if (c != ch [i]) { |
|
|
unread (c); |
|
|
if (i != 0) { |
|
|
unread (ch, i); |
|
|
} |
|
|
return false; |
|
|
} |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
5072 |
|
|
5073 |
|
// Compare the input, character- |
5074 |
|
// by character. |
5075 |
|
|
5076 |
|
for (int i = 0; i < ch.length; i++) |
5077 |
|
{ |
5078 |
|
c = readCh(); |
5079 |
|
if (c != ch[i]) |
5080 |
|
{ |
5081 |
|
unread(c); |
5082 |
|
if (i != 0) |
5083 |
|
{ |
5084 |
|
unread(ch, i); |
5085 |
|
} |
5086 |
|
return false; |
5087 |
|
} |
5088 |
|
} |
5089 |
|
return true; |
5090 |
|
} |
5091 |
|
|
5092 |
/** |
/** |
5093 |
* Return true if we can read some whitespace. |
* Return true if we can read some whitespace. |
5094 |
* <p>This is simply a convenience method. |
* <p>This is simply a convenience method. |
5095 |
* <p>This method will push back a character rather than an |
* <p>This method will push back a character rather than an |
5096 |
* array whenever possible (probably the majority of cases). |
* array whenever possible (probably the majority of cases). |
5097 |
* @return true if whitespace was found. |
* @return true if whitespace was found. |
5098 |
*/ |
*/ |
5099 |
private boolean tryWhitespace () |
private boolean tryWhitespace() |
5100 |
throws SAXException, IOException |
throws SAXException, IOException |
5101 |
{ |
{ |
5102 |
char c; |
char c; |
5103 |
c = readCh (); |
c = readCh(); |
5104 |
if (isWhitespace (c)) { |
if (isWhitespace(c)) |
5105 |
skipWhitespace (); |
{ |
5106 |
return true; |
skipWhitespace(); |
5107 |
} else { |
return true; |
5108 |
unread (c); |
} |
5109 |
return false; |
else |
5110 |
} |
{ |
5111 |
} |
unread(c); |
5112 |
|
return false; |
5113 |
|
} |
5114 |
/** |
} |
5115 |
* Read all data until we find the specified string. |
|
5116 |
* This is useful for scanning CDATA sections and PIs. |
/** |
5117 |
* <p>This is inefficient right now, since it calls tryRead () |
* Read all data until we find the specified string. |
5118 |
* for every character. |
* This is useful for scanning CDATA sections and PIs. |
5119 |
* @param delim The string delimiter |
* <p>This is inefficient right now, since it calls tryRead () |
5120 |
* @see #tryRead (String, boolean) |
* for every character. |
5121 |
* @see #readCh |
* @param delim The string delimiter |
5122 |
*/ |
* @see #tryRead (String, boolean) |
5123 |
private void parseUntil (String delim) |
* @see #readCh |
5124 |
|
*/ |
5125 |
|
private void parseUntil(String delim) |
5126 |
throws SAXException, IOException |
throws SAXException, IOException |
5127 |
{ |
{ |
5128 |
parseUntil (delim.toCharArray ()); |
parseUntil(delim.toCharArray()); |
5129 |
} |
} |
5130 |
|
|
5131 |
private void parseUntil (char delim []) |
private void parseUntil(char[] delim) |
5132 |
throws SAXException, IOException |
throws SAXException, IOException |
5133 |
{ |
{ |
5134 |
char c; |
char c; |
5135 |
int startLine = line; |
int startLine = line; |
5136 |
|
|
5137 |
try { |
try |
5138 |
while (!tryRead (delim)) { |
{ |
5139 |
c = readCh (); |
while (!tryRead(delim)) |
5140 |
dataBufferAppend (c); |
{ |
5141 |
} |
c = readCh(); |
5142 |
} catch (EOFException e) { |
dataBufferAppend(c); |
5143 |
error ("end of input while looking for delimiter " |
} |
5144 |
+ "(started on line " + startLine |
} |
5145 |
+ ')', null, new String (delim)); |
catch (EOFException e) |
5146 |
} |
{ |
5147 |
} |
error("end of input while looking for delimiter " |
5148 |
|
+ "(started on line " + startLine |
5149 |
|
+ ')', null, new String(delim)); |
5150 |
////////////////////////////////////////////////////////////////////// |
} |
5151 |
// Low-level I/O. |
} |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
5152 |
|
|
5153 |
/** |
////////////////////////////////////////////////////////////////////// |
5154 |
* Prefetch US-ASCII XML/text decl from input stream into read buffer. |
// Low-level I/O. |
5155 |
* Doesn't buffer more than absolutely needed, so that when an encoding |
////////////////////////////////////////////////////////////////////// |
5156 |
* decl says we need to create an InputStreamReader, we can discard our |
|
5157 |
* buffer and reset(). Caller knows the first chars of the decl exist |
/** |
5158 |
* in the input stream. |
* Prefetch US-ASCII XML/text decl from input stream into read buffer. |
5159 |
*/ |
* Doesn't buffer more than absolutely needed, so that when an encoding |
5160 |
private void prefetchASCIIEncodingDecl () |
* decl says we need to create an InputStreamReader, we can discard our |
5161 |
|
* buffer and reset(). Caller knows the first chars of the decl exist |
5162 |
|
* in the input stream. |
5163 |
|
*/ |
5164 |
|
private void prefetchASCIIEncodingDecl() |
5165 |
throws SAXException, IOException |
throws SAXException, IOException |
5166 |
{ |
{ |
5167 |
int ch; |
int ch; |
5168 |
readBufferPos = readBufferLength = 0; |
readBufferPos = readBufferLength = 0; |
5169 |
|
|
5170 |
is.mark (readBuffer.length); |
is.mark(readBuffer.length); |
5171 |
while (true) { |
while (true) |
5172 |
ch = is.read (); |
{ |
5173 |
readBuffer [readBufferLength++] = (char) ch; |
ch = is.read(); |
5174 |
switch (ch) { |
readBuffer[readBufferLength++] = (char) ch; |
5175 |
case (int) '>': |
switch (ch) |
5176 |
return; |
{ |
5177 |
case -1: |
case (int) '>': |
5178 |
error ("file ends before end of XML or encoding declaration.", |
return; |
5179 |
null, "?>"); |
case -1: |
5180 |
} |
error("file ends before end of XML or encoding declaration.", |
5181 |
if (readBuffer.length == readBufferLength) |
null, "?>"); |
5182 |
error ("unfinished XML or encoding declaration"); |
} |
5183 |
} |
if (readBuffer.length == readBufferLength) |
5184 |
} |
{ |
5185 |
|
error("unfinished XML or encoding declaration"); |
5186 |
|
} |
5187 |
|
} |
5188 |
|
} |
5189 |
|
|
5190 |
/** |
/** |
5191 |
* Read a chunk of data from an external input source. |
* Read a chunk of data from an external input source. |
5192 |
* <p>This is simply a front-end that fills the rawReadBuffer |
* <p>This is simply a front-end that fills the rawReadBuffer |
5193 |
* with bytes, then calls the appropriate encoding handler. |
* with bytes, then calls the appropriate encoding handler. |
5194 |
* @see #encoding |
* @see #encoding |
5195 |
* @see #rawReadBuffer |
* @see #rawReadBuffer |
5196 |
* @see #readBuffer |
* @see #readBuffer |
5197 |
* @see #filterCR |
* @see #filterCR |
5198 |
* @see #copyUtf8ReadBuffer |
* @see #copyUtf8ReadBuffer |
5199 |
* @see #copyIso8859_1ReadBuffer |
* @see #copyIso8859_1ReadBuffer |
5200 |
* @see #copyUcs_2ReadBuffer |
* @see #copyUcs_2ReadBuffer |
5201 |
* @see #copyUcs_4ReadBuffer |
* @see #copyUcs_4ReadBuffer |
5202 |
*/ |
*/ |
5203 |
private void readDataChunk () |
private void readDataChunk() |
5204 |
throws SAXException, IOException |
throws SAXException, IOException |
5205 |
{ |
{ |
5206 |
int count; |
int count; |
5207 |
|
|
5208 |
// See if we have any overflow (filterCR sets for CR at end) |
// See if we have any overflow (filterCR sets for CR at end) |
5209 |
if (readBufferOverflow > -1) { |
if (readBufferOverflow > -1) |
5210 |
readBuffer [0] = (char) readBufferOverflow; |
{ |
5211 |
readBufferOverflow = -1; |
readBuffer[0] = (char) readBufferOverflow; |
5212 |
readBufferPos = 1; |
readBufferOverflow = -1; |
5213 |
sawCR = true; |
readBufferPos = 1; |
5214 |
} else { |
sawCR = true; |
5215 |
readBufferPos = 0; |
} |
5216 |
sawCR = false; |
else |
5217 |
} |
{ |
5218 |
|
readBufferPos = 0; |
5219 |
// input from a character stream. |
sawCR = false; |
5220 |
if (sourceType == INPUT_READER) { |
} |
|
count = reader.read (readBuffer, |
|
|
readBufferPos, READ_BUFFER_MAX - readBufferPos); |
|
|
if (count < 0) |
|
|
readBufferLength = readBufferPos; |
|
|
else |
|
|
readBufferLength = readBufferPos + count; |
|
|
if (readBufferLength > 0) |
|
|
filterCR (count >= 0); |
|
|
sawCR = false; |
|
|
return; |
|
|
} |
|
|
|
|
|
// Read as many bytes as possible into the raw buffer. |
|
|
count = is.read (rawReadBuffer, 0, READ_BUFFER_MAX); |
|
|
|
|
|
// Dispatch to an encoding-specific reader method to populate |
|
|
// the readBuffer. In most parser speed profiles, these routines |
|
|
// show up at the top of the CPU usage chart. |
|
|
if (count > 0) { |
|
|
switch (encoding) { |
|
|
// one byte builtins |
|
|
case ENCODING_ASCII: |
|
|
copyIso8859_1ReadBuffer (count, (char) 0x0080); |
|
|
break; |
|
|
case ENCODING_UTF_8: |
|
|
copyUtf8ReadBuffer (count); |
|
|
break; |
|
|
case ENCODING_ISO_8859_1: |
|
|
copyIso8859_1ReadBuffer (count, (char) 0); |
|
|
break; |
|
|
|
|
|
// two byte builtins |
|
|
case ENCODING_UCS_2_12: |
|
|
copyUcs2ReadBuffer (count, 8, 0); |
|
|
break; |
|
|
case ENCODING_UCS_2_21: |
|
|
copyUcs2ReadBuffer (count, 0, 8); |
|
|
break; |
|
|
|
|
|
// four byte builtins |
|
|
case ENCODING_UCS_4_1234: |
|
|
copyUcs4ReadBuffer (count, 24, 16, 8, 0); |
|
|
break; |
|
|
case ENCODING_UCS_4_4321: |
|
|
copyUcs4ReadBuffer (count, 0, 8, 16, 24); |
|
|
break; |
|
|
case ENCODING_UCS_4_2143: |
|
|
copyUcs4ReadBuffer (count, 16, 24, 0, 8); |
|
|
break; |
|
|
case ENCODING_UCS_4_3412: |
|
|
copyUcs4ReadBuffer (count, 8, 0, 24, 16); |
|
|
break; |
|
|
} |
|
|
} else |
|
|
readBufferLength = readBufferPos; |
|
|
|
|
|
readBufferPos = 0; |
|
|
|
|
|
// Filter out all carriage returns if we've seen any |
|
|
// (including any saved from a previous read) |
|
|
if (sawCR) { |
|
|
filterCR (count >= 0); |
|
|
sawCR = false; |
|
|
|
|
|
// must actively report EOF, lest some CRs get lost. |
|
|
if (readBufferLength == 0 && count >= 0) |
|
|
readDataChunk (); |
|
|
} |
|
5221 |
|
|
5222 |
if (count > 0) |
// input from a character stream. |
5223 |
currentByteCount += count; |
if (sourceType == INPUT_READER) |
5224 |
} |
{ |
5225 |
|
count = reader.read(readBuffer, |
5226 |
|
readBufferPos, READ_BUFFER_MAX - readBufferPos); |
5227 |
|
if (count < 0) |
5228 |
|
{ |
5229 |
|
readBufferLength = readBufferPos; |
5230 |
|
} |
5231 |
|
else |
5232 |
|
{ |
5233 |
|
readBufferLength = readBufferPos + count; |
5234 |
|
} |
5235 |
|
if (readBufferLength > 0) |
5236 |
|
{ |
5237 |
|
filterCR(count >= 0); |
5238 |
|
} |
5239 |
|
sawCR = false; |
5240 |
|
return; |
5241 |
|
} |
5242 |
|
|
5243 |
|
// Read as many bytes as possible into the raw buffer. |
5244 |
|
count = is.read(rawReadBuffer, 0, READ_BUFFER_MAX); |
5245 |
|
|
5246 |
|
// Dispatch to an encoding-specific reader method to populate |
5247 |
|
// the readBuffer. In most parser speed profiles, these routines |
5248 |
|
// show up at the top of the CPU usage chart. |
5249 |
|
if (count > 0) |
5250 |
|
{ |
5251 |
|
switch (encoding) |
5252 |
|
{ |
5253 |
|
// one byte builtins |
5254 |
|
case ENCODING_ASCII: |
5255 |
|
copyIso8859_1ReadBuffer(count, (char) 0x0080); |
5256 |
|
break; |
5257 |
|
case ENCODING_UTF_8: |
5258 |
|
copyUtf8ReadBuffer(count); |
5259 |
|
break; |
5260 |
|
case ENCODING_ISO_8859_1: |
5261 |
|
copyIso8859_1ReadBuffer(count, (char) 0); |
5262 |
|
break; |
5263 |
|
|
5264 |
/** |
// two byte builtins |
5265 |
* Filter carriage returns in the read buffer. |
case ENCODING_UCS_2_12: |
5266 |
* CRLF becomes LF; CR becomes LF. |
copyUcs2ReadBuffer(count, 8, 0); |
5267 |
* @param moreData true iff more data might come from the same source |
break; |
5268 |
* @see #readDataChunk |
case ENCODING_UCS_2_21: |
5269 |
* @see #readBuffer |
copyUcs2ReadBuffer(count, 0, 8); |
5270 |
* @see #readBufferOverflow |
break; |
5271 |
*/ |
|
5272 |
private void filterCR (boolean moreData) |
// four byte builtins |
5273 |
{ |
case ENCODING_UCS_4_1234: |
5274 |
int i, j; |
copyUcs4ReadBuffer(count, 24, 16, 8, 0); |
5275 |
|
break; |
5276 |
|
case ENCODING_UCS_4_4321: |
5277 |
|
copyUcs4ReadBuffer(count, 0, 8, 16, 24); |
5278 |
|
break; |
5279 |
|
case ENCODING_UCS_4_2143: |
5280 |
|
copyUcs4ReadBuffer(count, 16, 24, 0, 8); |
5281 |
|
break; |
5282 |
|
case ENCODING_UCS_4_3412: |
5283 |
|
copyUcs4ReadBuffer(count, 8, 0, 24, 16); |
5284 |
|
break; |
5285 |
|
} |
5286 |
|
} |
5287 |
|
else |
5288 |
|
{ |
5289 |
|
readBufferLength = readBufferPos; |
5290 |
|
} |
5291 |
|
|
5292 |
readBufferOverflow = -1; |
readBufferPos = 0; |
5293 |
|
|
5294 |
|
// Filter out all carriage returns if we've seen any |
5295 |
|
// (including any saved from a previous read) |
5296 |
|
if (sawCR) |
5297 |
|
{ |
5298 |
|
filterCR(count >= 0); |
5299 |
|
sawCR = false; |
5300 |
|
|
5301 |
|
// must actively report EOF, lest some CRs get lost. |
5302 |
|
if (readBufferLength == 0 && count >= 0) |
5303 |
|
{ |
5304 |
|
readDataChunk(); |
5305 |
|
} |
5306 |
|
} |
5307 |
|
|
5308 |
|
if (count > 0) |
5309 |
|
{ |
5310 |
|
currentByteCount += count; |
5311 |
|
} |
5312 |
|
} |
5313 |
|
|
5314 |
|
/** |
5315 |
|
* Filter carriage returns in the read buffer. |
5316 |
|
* CRLF becomes LF; CR becomes LF. |
5317 |
|
* @param moreData true iff more data might come from the same source |
5318 |
|
* @see #readDataChunk |
5319 |
|
* @see #readBuffer |
5320 |
|
* @see #readBufferOverflow |
5321 |
|
*/ |
5322 |
|
private void filterCR(boolean moreData) |
5323 |
|
{ |
5324 |
|
int i, j; |
5325 |
|
|
5326 |
|
readBufferOverflow = -1; |
5327 |
|
|
5328 |
loop: |
loop: |
5329 |
for (i = j = readBufferPos; j < readBufferLength; i++, j++) { |
for (i = j = readBufferPos; j < readBufferLength; i++, j++) |
5330 |
switch (readBuffer [j]) { |
{ |
5331 |
case '\r': |
switch (readBuffer[j]) |
5332 |
if (j == readBufferLength - 1) { |
{ |
5333 |
if (moreData) { |
case '\r': |
5334 |
readBufferOverflow = '\r'; |
if (j == readBufferLength - 1) |
5335 |
readBufferLength--; |
{ |
5336 |
} else // CR at end of buffer |
if (moreData) |
5337 |
readBuffer [i++] = '\n'; |
{ |
5338 |
break loop; |
readBufferOverflow = '\r'; |
5339 |
} else if (readBuffer [j + 1] == '\n') { |
readBufferLength--; |
5340 |
j++; |
} |
5341 |
} |
else // CR at end of buffer |
5342 |
readBuffer [i] = '\n'; |
{ |
5343 |
break; |
readBuffer[i++] = '\n'; |
5344 |
|
} |
5345 |
case '\n': |
break loop; |
5346 |
default: |
} |
5347 |
readBuffer [i] = readBuffer [j]; |
else if (readBuffer[j + 1] == '\n') |
5348 |
break; |
{ |
5349 |
} |
j++; |
5350 |
} |
} |
5351 |
readBufferLength = i; |
readBuffer[i] = '\n'; |
5352 |
} |
break; |
|
|
|
|
/** |
|
|
* Convert a buffer of UTF-8-encoded bytes into UTF-16 characters. |
|
|
* <p>When readDataChunk () calls this method, the raw bytes are in |
|
|
* rawReadBuffer, and the final characters will appear in |
|
|
* readBuffer. |
|
|
* <p>Note that as of Unicode 3.1, good practice became a requirement, |
|
|
* so that each Unicode character has exactly one UTF-8 representation. |
|
|
* @param count The number of bytes to convert. |
|
|
* @see #readDataChunk |
|
|
* @see #rawReadBuffer |
|
|
* @see #readBuffer |
|
|
* @see #getNextUtf8Byte |
|
|
*/ |
|
|
private void copyUtf8ReadBuffer (int count) |
|
|
throws SAXException, IOException |
|
|
{ |
|
|
int i = 0; |
|
|
int j = readBufferPos; |
|
|
int b1; |
|
|
char c = 0; |
|
|
|
|
|
/* |
|
|
// check once, so the runtime won't (if it's smart enough) |
|
|
if (count < 0 || count > rawReadBuffer.length) |
|
|
throw new ArrayIndexOutOfBoundsException (Integer.toString (count)); |
|
|
*/ |
|
|
|
|
|
while (i < count) { |
|
|
b1 = rawReadBuffer [i++]; |
|
|
|
|
|
// Determine whether we are dealing |
|
|
// with a one-, two-, three-, or four- |
|
|
// byte sequence. |
|
|
if (b1 < 0) { |
|
|
if ((b1 & 0xe0) == 0xc0) { |
|
|
// 2-byte sequence: 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx |
|
|
c = (char) (((b1 & 0x1f) << 6) |
|
|
| getNextUtf8Byte (i++, count)); |
|
|
if (c < 0x0080) |
|
|
encodingError ("Illegal two byte UTF-8 sequence", |
|
|
c, 0); |
|
|
//Sec 2.11 |
|
|
// [1] the two-character sequence #xD #xA |
|
|
// [2] the two-character sequence #xD #x85 |
|
|
if ((c == 0x0085 || c == 0x000a) && sawCR) |
|
|
continue; |
|
|
|
|
|
// Sec 2.11 |
|
|
// [3] the single character #x85 |
|
|
|
|
|
if(c == 0x0085 && xmlVersion == XML_11) |
|
|
readBuffer[j++] = '\r'; |
|
|
} else if ((b1 & 0xf0) == 0xe0) { |
|
|
// 3-byte sequence: |
|
|
// zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx |
|
|
// most CJKV characters |
|
|
c = (char) (((b1 & 0x0f) << 12) | |
|
|
(getNextUtf8Byte (i++, count) << 6) | |
|
|
getNextUtf8Byte (i++, count)); |
|
|
//sec 2.11 |
|
|
//[4] the single character #x2028 |
|
|
if(c == 0x2028 && xmlVersion == XML_11){ |
|
|
readBuffer[j++] = '\r'; |
|
|
sawCR = true; |
|
|
continue; |
|
|
} |
|
|
if (c < 0x0800 || (c >= 0xd800 && c <= 0xdfff)) |
|
|
encodingError ("Illegal three byte UTF-8 sequence", |
|
|
c, 0); |
|
|
} else if ((b1 & 0xf8) == 0xf0) { |
|
|
// 4-byte sequence: 11101110wwwwzzzzyy + 110111yyyyxxxxxx |
|
|
// = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx |
|
|
// (uuuuu = wwww + 1) |
|
|
// "Surrogate Pairs" ... from the "Astral Planes" |
|
|
// Unicode 3.1 assigned the first characters there |
|
|
int iso646 = b1 & 07; |
|
|
iso646 = (iso646 << 6) + getNextUtf8Byte (i++, count); |
|
|
iso646 = (iso646 << 6) + getNextUtf8Byte (i++, count); |
|
|
iso646 = (iso646 << 6) + getNextUtf8Byte (i++, count); |
|
|
|
|
|
if (iso646 <= 0xffff) { |
|
|
encodingError ("Illegal four byte UTF-8 sequence", |
|
|
iso646, 0); |
|
|
} else { |
|
|
if (iso646 > 0x0010ffff) |
|
|
encodingError ( |
|
|
"UTF-8 value out of range for Unicode", |
|
|
iso646, 0); |
|
|
iso646 -= 0x010000; |
|
|
readBuffer [j++] = (char) (0xd800 | (iso646 >> 10)); |
|
|
readBuffer [j++] = (char) (0xdc00 | (iso646 & 0x03ff)); |
|
|
continue; |
|
|
} |
|
|
} else { |
|
|
// The five and six byte encodings aren't supported; |
|
|
// they exceed the Unicode (and XML) range. |
|
|
encodingError ( |
|
|
"unsupported five or six byte UTF-8 sequence", |
|
|
0xff & b1, i); |
|
|
// NOTREACHED |
|
|
c = 0; |
|
|
} |
|
|
} else { |
|
|
// 1-byte sequence: 000000000xxxxxxx = 0xxxxxxx |
|
|
// (US-ASCII character, "common" case, one branch to here) |
|
|
c = (char) b1; |
|
|
} |
|
|
readBuffer [j++] = c; |
|
|
if (c == '\r') |
|
|
sawCR = true; |
|
|
} |
|
|
// How many characters have we read? |
|
|
readBufferLength = j; |
|
|
} |
|
5353 |
|
|
5354 |
|
case '\n': |
5355 |
|
default: |
5356 |
|
readBuffer[i] = readBuffer[j]; |
5357 |
|
break; |
5358 |
|
} |
5359 |
|
} |
5360 |
|
readBufferLength = i; |
5361 |
|
} |
5362 |
|
|
5363 |
/** |
/** |
5364 |
* Return the next byte value in a UTF-8 sequence. |
* Convert a buffer of UTF-8-encoded bytes into UTF-16 characters. |
5365 |
* If it is not possible to get a byte from the current |
* <p>When readDataChunk () calls this method, the raw bytes are in |
5366 |
* entity, throw an exception. |
* rawReadBuffer, and the final characters will appear in |
5367 |
* @param pos The current position in the rawReadBuffer. |
* readBuffer. |
5368 |
* @param count The number of bytes in the rawReadBuffer |
* <p>Note that as of Unicode 3.1, good practice became a requirement, |
5369 |
* @return The significant six bits of a non-initial byte in |
* so that each Unicode character has exactly one UTF-8 representation. |
5370 |
* a UTF-8 sequence. |
* @param count The number of bytes to convert. |
5371 |
* @exception EOFException If the sequence is incomplete. |
* @see #readDataChunk |
5372 |
*/ |
* @see #rawReadBuffer |
5373 |
private int getNextUtf8Byte (int pos, int count) |
* @see #readBuffer |
5374 |
|
* @see #getNextUtf8Byte |
5375 |
|
*/ |
5376 |
|
private void copyUtf8ReadBuffer(int count) |
5377 |
throws SAXException, IOException |
throws SAXException, IOException |
5378 |
{ |
{ |
5379 |
int val; |
int i = 0; |
5380 |
|
int j = readBufferPos; |
5381 |
|
int b1; |
5382 |
|
char c = 0; |
5383 |
|
|
5384 |
|
/* |
5385 |
|
// check once, so the runtime won't (if it's smart enough) |
5386 |
|
if (count < 0 || count > rawReadBuffer.length) |
5387 |
|
throw new ArrayIndexOutOfBoundsException (Integer.toString (count)); |
5388 |
|
*/ |
5389 |
|
|
5390 |
// Take a character from the buffer |
while (i < count) |
5391 |
// or from the actual input stream. |
{ |
5392 |
if (pos < count) { |
b1 = rawReadBuffer[i++]; |
5393 |
val = rawReadBuffer [pos]; |
|
5394 |
} else { |
// Determine whether we are dealing |
5395 |
val = is.read (); |
// with a one-, two-, three-, or four- |
5396 |
if (val == -1) { |
// byte sequence. |
5397 |
encodingError ("unfinished multi-byte UTF-8 sequence at EOF", |
if (b1 < 0) |
5398 |
-1, pos); |
{ |
5399 |
} |
if ((b1 & 0xe0) == 0xc0) |
5400 |
} |
{ |
5401 |
|
// 2-byte sequence: 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx |
5402 |
// Check for the correct bits at the start. |
c = (char) (((b1 & 0x1f) << 6) |
5403 |
if ((val & 0xc0) != 0x80) { |
| getNextUtf8Byte(i++, count)); |
5404 |
encodingError ("bad continuation of multi-byte UTF-8 sequence", |
if (c < 0x0080) |
5405 |
val, pos + 1); |
{ |
5406 |
} |
encodingError("Illegal two byte UTF-8 sequence", |
5407 |
|
c, 0); |
5408 |
|
} |
5409 |
|
|
5410 |
|
//Sec 2.11 |
5411 |
|
// [1] the two-character sequence #xD #xA |
5412 |
|
// [2] the two-character sequence #xD #x85 |
5413 |
|
if ((c == 0x0085 || c == 0x000a) && sawCR) |
5414 |
|
{ |
5415 |
|
continue; |
5416 |
|
} |
5417 |
|
|
5418 |
|
// Sec 2.11 |
5419 |
|
// [3] the single character #x85 |
5420 |
|
|
5421 |
|
if (c == 0x0085 && xmlVersion == XML_11) |
5422 |
|
{ |
5423 |
|
readBuffer[j++] = '\r'; |
5424 |
|
} |
5425 |
|
} |
5426 |
|
else if ((b1 & 0xf0) == 0xe0) |
5427 |
|
{ |
5428 |
|
// 3-byte sequence: |
5429 |
|
// zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx |
5430 |
|
// most CJKV characters |
5431 |
|
c = (char) (((b1 & 0x0f) << 12) | |
5432 |
|
(getNextUtf8Byte(i++, count) << 6) | |
5433 |
|
getNextUtf8Byte(i++, count)); |
5434 |
|
//sec 2.11 |
5435 |
|
//[4] the single character #x2028 |
5436 |
|
if (c == 0x2028 && xmlVersion == XML_11) |
5437 |
|
{ |
5438 |
|
readBuffer[j++] = '\r'; |
5439 |
|
sawCR = true; |
5440 |
|
continue; |
5441 |
|
} |
5442 |
|
if (c < 0x0800 || (c >= 0xd800 && c <= 0xdfff)) |
5443 |
|
{ |
5444 |
|
encodingError("Illegal three byte UTF-8 sequence", |
5445 |
|
c, 0); |
5446 |
|
} |
5447 |
|
} |
5448 |
|
else if ((b1 & 0xf8) == 0xf0) |
5449 |
|
{ |
5450 |
|
// 4-byte sequence: 11101110wwwwzzzzyy + 110111yyyyxxxxxx |
5451 |
|
// = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx |
5452 |
|
// (uuuuu = wwww + 1) |
5453 |
|
// "Surrogate Pairs" ... from the "Astral Planes" |
5454 |
|
// Unicode 3.1 assigned the first characters there |
5455 |
|
int iso646 = b1 & 07; |
5456 |
|
iso646 = (iso646 << 6) + getNextUtf8Byte(i++, count); |
5457 |
|
iso646 = (iso646 << 6) + getNextUtf8Byte(i++, count); |
5458 |
|
iso646 = (iso646 << 6) + getNextUtf8Byte(i++, count); |
5459 |
|
|
5460 |
|
if (iso646 <= 0xffff) |
5461 |
|
{ |
5462 |
|
encodingError("Illegal four byte UTF-8 sequence", |
5463 |
|
iso646, 0); |
5464 |
|
} |
5465 |
|
else |
5466 |
|
{ |
5467 |
|
if (iso646 > 0x0010ffff) |
5468 |
|
{ |
5469 |
|
encodingError("UTF-8 value out of range for Unicode", |
5470 |
|
iso646, 0); |
5471 |
|
} |
5472 |
|
iso646 -= 0x010000; |
5473 |
|
readBuffer[j++] = (char) (0xd800 | (iso646 >> 10)); |
5474 |
|
readBuffer[j++] = (char) (0xdc00 | (iso646 & 0x03ff)); |
5475 |
|
continue; |
5476 |
|
} |
5477 |
|
} |
5478 |
|
else |
5479 |
|
{ |
5480 |
|
// The five and six byte encodings aren't supported; |
5481 |
|
// they exceed the Unicode (and XML) range. |
5482 |
|
encodingError("unsupported five or six byte UTF-8 sequence", |
5483 |
|
0xff & b1, i); |
5484 |
|
// NOTREACHED |
5485 |
|
c = 0; |
5486 |
|
} |
5487 |
|
} |
5488 |
|
else |
5489 |
|
{ |
5490 |
|
// 1-byte sequence: 000000000xxxxxxx = 0xxxxxxx |
5491 |
|
// (US-ASCII character, "common" case, one branch to here) |
5492 |
|
c = (char) b1; |
5493 |
|
} |
5494 |
|
readBuffer[j++] = c; |
5495 |
|
if (c == '\r') |
5496 |
|
{ |
5497 |
|
sawCR = true; |
5498 |
|
} |
5499 |
|
} |
5500 |
|
// How many characters have we read? |
5501 |
|
readBufferLength = j; |
5502 |
|
} |
5503 |
|
|
5504 |
|
/** |
5505 |
|
* Return the next byte value in a UTF-8 sequence. |
5506 |
|
* If it is not possible to get a byte from the current |
5507 |
|
* entity, throw an exception. |
5508 |
|
* @param pos The current position in the rawReadBuffer. |
5509 |
|
* @param count The number of bytes in the rawReadBuffer |
5510 |
|
* @return The significant six bits of a non-initial byte in |
5511 |
|
* a UTF-8 sequence. |
5512 |
|
* @exception EOFException If the sequence is incomplete. |
5513 |
|
*/ |
5514 |
|
private int getNextUtf8Byte(int pos, int count) |
5515 |
|
throws SAXException, IOException |
5516 |
|
{ |
5517 |
|
int val; |
5518 |
|
|
5519 |
|
// Take a character from the buffer |
5520 |
|
// or from the actual input stream. |
5521 |
|
if (pos < count) |
5522 |
|
{ |
5523 |
|
val = rawReadBuffer[pos]; |
5524 |
|
} |
5525 |
|
else |
5526 |
|
{ |
5527 |
|
val = is.read(); |
5528 |
|
if (val == -1) |
5529 |
|
{ |
5530 |
|
encodingError("unfinished multi-byte UTF-8 sequence at EOF", |
5531 |
|
-1, pos); |
5532 |
|
} |
5533 |
|
} |
5534 |
|
|
5535 |
// Return the significant bits. |
// Check for the correct bits at the start. |
5536 |
return (val & 0x3f); |
if ((val & 0xc0) != 0x80) |
5537 |
} |
{ |
5538 |
|
encodingError("bad continuation of multi-byte UTF-8 sequence", |
5539 |
|
val, pos + 1); |
5540 |
|
} |
5541 |
|
|
5542 |
|
// Return the significant bits. |
5543 |
|
return (val & 0x3f); |
5544 |
|
} |
5545 |
|
|
5546 |
/** |
/** |
5547 |
* Convert a buffer of US-ASCII or ISO-8859-1-encoded bytes into |
* Convert a buffer of US-ASCII or ISO-8859-1-encoded bytes into |
5548 |
* UTF-16 characters. |
* UTF-16 characters. |
5549 |
* |
* |
5550 |
* <p>When readDataChunk () calls this method, the raw bytes are in |
* <p>When readDataChunk () calls this method, the raw bytes are in |
5551 |
* rawReadBuffer, and the final characters will appear in |
* rawReadBuffer, and the final characters will appear in |
5552 |
* readBuffer. |
* readBuffer. |
5553 |
* |
* |
5554 |
* @param count The number of bytes to convert. |
* @param count The number of bytes to convert. |
5555 |
* @param mask For ASCII conversion, 0x7f; else, 0xff. |
* @param mask For ASCII conversion, 0x7f; else, 0xff. |
5556 |
* @see #readDataChunk |
* @see #readDataChunk |
5557 |
* @see #rawReadBuffer |
* @see #rawReadBuffer |
5558 |
* @see #readBuffer |
* @see #readBuffer |
5559 |
*/ |
*/ |
5560 |
private void copyIso8859_1ReadBuffer (int count, char mask) |
private void copyIso8859_1ReadBuffer(int count, char mask) |
5561 |
throws IOException |
throws IOException |
5562 |
{ |
{ |
5563 |
int i, j; |
int i, j; |
5564 |
for (i = 0, j = readBufferPos; i < count; i++, j++) { |
for (i = 0, j = readBufferPos; i < count; i++, j++) |
5565 |
char c = (char) (rawReadBuffer [i] & 0xff); |
{ |
5566 |
if ((c & mask) != 0) |
char c = (char) (rawReadBuffer[i] & 0xff); |
5567 |
throw new CharConversionException ("non-ASCII character U+" |
if ((c & mask) != 0) |
5568 |
+ Integer.toHexString (c)); |
{ |
5569 |
if (c == 0x0085 && xmlVersion == XML_11) |
throw new CharConversionException("non-ASCII character U+" |
5570 |
c = '\r'; |
+ Integer.toHexString(c)); |
5571 |
readBuffer [j] = c; |
} |
5572 |
if (c == '\r') { |
if (c == 0x0085 && xmlVersion == XML_11) |
5573 |
sawCR = true; |
{ |
5574 |
} |
c = '\r'; |
5575 |
} |
} |
5576 |
readBufferLength = j; |
readBuffer[j] = c; |
5577 |
} |
if (c == '\r') |
5578 |
|
{ |
5579 |
|
sawCR = true; |
5580 |
|
} |
5581 |
|
} |
5582 |
|
readBufferLength = j; |
5583 |
|
} |
5584 |
|
|
5585 |
/** |
/** |
5586 |
* Convert a buffer of UCS-2-encoded bytes into UTF-16 characters |
* Convert a buffer of UCS-2-encoded bytes into UTF-16 characters |
5587 |
* (as used in Java string manipulation). |
* (as used in Java string manipulation). |
5588 |
* |
* |
5589 |
* <p>When readDataChunk () calls this method, the raw bytes are in |
* <p>When readDataChunk () calls this method, the raw bytes are in |
5590 |
* rawReadBuffer, and the final characters will appear in |
* rawReadBuffer, and the final characters will appear in |
5591 |
* readBuffer. |
* readBuffer. |
5592 |
* @param count The number of bytes to convert. |
* @param count The number of bytes to convert. |
5593 |
* @param shift1 The number of bits to shift byte 1. |
* @param shift1 The number of bits to shift byte 1. |
5594 |
* @param shift2 The number of bits to shift byte 2 |
* @param shift2 The number of bits to shift byte 2 |
5595 |
* @see #readDataChunk |
* @see #readDataChunk |
5596 |
* @see #rawReadBuffer |
* @see #rawReadBuffer |
5597 |
* @see #readBuffer |
* @see #readBuffer |
5598 |
*/ |
*/ |
5599 |
private void copyUcs2ReadBuffer (int count, int shift1, int shift2) |
private void copyUcs2ReadBuffer(int count, int shift1, int shift2) |
5600 |
throws SAXException |
throws SAXException |
5601 |
{ |
{ |
5602 |
int j = readBufferPos; |
int j = readBufferPos; |
5603 |
|
|
5604 |
if (count > 0 && (count % 2) != 0) { |
if (count > 0 && (count % 2) != 0) |
5605 |
encodingError ("odd number of bytes in UCS-2 encoding", -1, count); |
{ |
5606 |
} |
encodingError("odd number of bytes in UCS-2 encoding", -1, count); |
5607 |
// The loops are faster with less internal brancing; hence two |
} |
5608 |
if (shift1 == 0) { // "UTF-16-LE" |
// The loops are faster with less internal brancing; hence two |
5609 |
for (int i = 0; i < count; i += 2) { |
if (shift1 == 0) |
5610 |
char c = (char) (rawReadBuffer [i + 1] << 8); |
{ // "UTF-16-LE" |
5611 |
c |= 0xff & rawReadBuffer [i]; |
for (int i = 0; i < count; i += 2) |
5612 |
readBuffer [j++] = c; |
{ |
5613 |
if (c == '\r') |
char c = (char) (rawReadBuffer[i + 1] << 8); |
5614 |
sawCR = true; |
c |= 0xff & rawReadBuffer[i]; |
5615 |
} |
readBuffer[j++] = c; |
5616 |
} else { // "UTF-16-BE" |
if (c == '\r') |
5617 |
for (int i = 0; i < count; i += 2) { |
{ |
5618 |
char c = (char) (rawReadBuffer [i] << 8); |
sawCR = true; |
5619 |
c |= 0xff & rawReadBuffer [i + 1]; |
} |
5620 |
readBuffer [j++] = c; |
} |
5621 |
if (c == '\r') |
} |
5622 |
sawCR = true; |
else |
5623 |
} |
{ // "UTF-16-BE" |
5624 |
} |
for (int i = 0; i < count; i += 2) |
5625 |
readBufferLength = j; |
{ |
5626 |
} |
char c = (char) (rawReadBuffer[i] << 8); |
5627 |
|
c |= 0xff & rawReadBuffer[i + 1]; |
5628 |
|
readBuffer[j++] = c; |
5629 |
|
if (c == '\r') |
5630 |
|
{ |
5631 |
|
sawCR = true; |
5632 |
|
} |
5633 |
|
} |
5634 |
|
} |
5635 |
|
readBufferLength = j; |
5636 |
|
} |
5637 |
|
|
5638 |
|
/** |
5639 |
|
* Convert a buffer of UCS-4-encoded bytes into UTF-16 characters. |
5640 |
|
* |
5641 |
|
* <p>When readDataChunk () calls this method, the raw bytes are in |
5642 |
|
* rawReadBuffer, and the final characters will appear in |
5643 |
|
* readBuffer. |
5644 |
|
* <p>Java has Unicode chars, and this routine uses surrogate pairs |
5645 |
|
* for ISO-10646 values between 0x00010000 and 0x000fffff. An |
5646 |
|
* exception is thrown if the ISO-10646 character has no Unicode |
5647 |
|
* representation. |
5648 |
|
* |
5649 |
|
* @param count The number of bytes to convert. |
5650 |
|
* @param shift1 The number of bits to shift byte 1. |
5651 |
|
* @param shift2 The number of bits to shift byte 2 |
5652 |
|
* @param shift3 The number of bits to shift byte 2 |
5653 |
|
* @param shift4 The number of bits to shift byte 2 |
5654 |
|
* @see #readDataChunk |
5655 |
|
* @see #rawReadBuffer |
5656 |
|
* @see #readBuffer |
5657 |
|
*/ |
5658 |
|
private void copyUcs4ReadBuffer(int count, int shift1, int shift2, |
5659 |
|
int shift3, int shift4) |
5660 |
|
throws SAXException |
5661 |
|
{ |
5662 |
|
int j = readBufferPos; |
5663 |
|
|
5664 |
|
if (count > 0 && (count % 4) != 0) |
5665 |
|
{ |
5666 |
|
encodingError("number of bytes in UCS-4 encoding " + |
5667 |
|
"not divisible by 4", |
5668 |
|
-1, count); |
5669 |
|
} |
5670 |
|
for (int i = 0; i < count; i += 4) |
5671 |
|
{ |
5672 |
|
int value = (((rawReadBuffer [i] & 0xff) << shift1) | |
5673 |
|
((rawReadBuffer [i + 1] & 0xff) << shift2) | |
5674 |
|
((rawReadBuffer [i + 2] & 0xff) << shift3) | |
5675 |
|
((rawReadBuffer [i + 3] & 0xff) << shift4)); |
5676 |
|
if (value < 0x0000ffff) |
5677 |
|
{ |
5678 |
|
readBuffer [j++] = (char) value; |
5679 |
|
if (value == (int) '\r') |
5680 |
|
{ |
5681 |
|
sawCR = true; |
5682 |
|
} |
5683 |
|
} |
5684 |
|
else if (value < 0x0010ffff) |
5685 |
|
{ |
5686 |
|
value -= 0x010000; |
5687 |
|
readBuffer[j++] = (char) (0xd8 | ((value >> 10) & 0x03ff)); |
5688 |
|
readBuffer[j++] = (char) (0xdc | (value & 0x03ff)); |
5689 |
|
} |
5690 |
|
else |
5691 |
|
{ |
5692 |
|
encodingError("UCS-4 value out of range for Unicode", |
5693 |
|
value, i); |
5694 |
|
} |
5695 |
|
} |
5696 |
|
readBufferLength = j; |
5697 |
|
} |
5698 |
|
|
5699 |
/** |
/** |
5700 |
* Convert a buffer of UCS-4-encoded bytes into UTF-16 characters. |
* Report a character encoding error. |
5701 |
* |
*/ |
5702 |
* <p>When readDataChunk () calls this method, the raw bytes are in |
private void encodingError(String message, int value, int offset) |
|
* rawReadBuffer, and the final characters will appear in |
|
|
* readBuffer. |
|
|
* <p>Java has Unicode chars, and this routine uses surrogate pairs |
|
|
* for ISO-10646 values between 0x00010000 and 0x000fffff. An |
|
|
* exception is thrown if the ISO-10646 character has no Unicode |
|
|
* representation. |
|
|
* |
|
|
* @param count The number of bytes to convert. |
|
|
* @param shift1 The number of bits to shift byte 1. |
|
|
* @param shift2 The number of bits to shift byte 2 |
|
|
* @param shift3 The number of bits to shift byte 2 |
|
|
* @param shift4 The number of bits to shift byte 2 |
|
|
* @see #readDataChunk |
|
|
* @see #rawReadBuffer |
|
|
* @see #readBuffer |
|
|
*/ |
|
|
private void copyUcs4ReadBuffer (int count, int shift1, int shift2, |
|
|
int shift3, int shift4) |
|
5703 |
throws SAXException |
throws SAXException |
5704 |
{ |
{ |
5705 |
int j = readBufferPos; |
if (value != -1) |
5706 |
|
{ |
5707 |
|
message = message + " (character code: 0x" + |
5708 |
|
Integer.toHexString(value) + ')'; |
5709 |
|
error(message); |
5710 |
|
} |
5711 |
|
} |
5712 |
|
|
5713 |
|
////////////////////////////////////////////////////////////////////// |
5714 |
|
// Local Variables. |
5715 |
|
////////////////////////////////////////////////////////////////////// |
5716 |
|
|
5717 |
|
/** |
5718 |
|
* Re-initialize the variables for each parse. |
5719 |
|
*/ |
5720 |
|
private void initializeVariables() |
5721 |
|
{ |
5722 |
|
// First line |
5723 |
|
line = 1; |
5724 |
|
column = 0; |
5725 |
|
|
5726 |
|
// Set up the buffers for data and names |
5727 |
|
dataBufferPos = 0; |
5728 |
|
dataBuffer = new char[DATA_BUFFER_INITIAL]; |
5729 |
|
nameBufferPos = 0; |
5730 |
|
nameBuffer = new char[NAME_BUFFER_INITIAL]; |
5731 |
|
|
5732 |
|
// Set up the DTD hash tables |
5733 |
|
elementInfo = new HashMap(); |
5734 |
|
entityInfo = new HashMap(); |
5735 |
|
notationInfo = new HashMap(); |
5736 |
|
skippedPE = false; |
5737 |
|
|
5738 |
|
// Set up the variables for the current |
5739 |
|
// element context. |
5740 |
|
currentElement = null; |
5741 |
|
currentElementContent = CONTENT_UNDECLARED; |
5742 |
|
|
5743 |
|
// Set up the input variables |
5744 |
|
sourceType = INPUT_NONE; |
5745 |
|
inputStack = new LinkedList(); |
5746 |
|
entityStack = new LinkedList(); |
5747 |
|
externalEntity = null; |
5748 |
|
tagAttributePos = 0; |
5749 |
|
tagAttributes = new String[100]; |
5750 |
|
rawReadBuffer = new byte[READ_BUFFER_MAX]; |
5751 |
|
readBufferOverflow = -1; |
5752 |
|
|
5753 |
|
scratch = new InputSource(); |
5754 |
|
|
5755 |
|
inLiteral = false; |
5756 |
|
expandPE = false; |
5757 |
|
peIsError = false; |
5758 |
|
|
5759 |
|
doReport = false; |
5760 |
|
|
5761 |
|
inCDATA = false; |
5762 |
|
|
5763 |
|
symbolTable = new Object[SYMBOL_TABLE_LENGTH][]; |
5764 |
|
} |
5765 |
|
|
5766 |
if (count > 0 && (count % 4) != 0) { |
static class ExternalIdentifiers |
5767 |
encodingError ( |
{ |
|
"number of bytes in UCS-4 encoding not divisible by 4", |
|
|
-1, count); |
|
|
} |
|
|
for (int i = 0; i < count; i += 4) { |
|
|
int value = (((rawReadBuffer [i] & 0xff) << shift1) | |
|
|
((rawReadBuffer [i + 1] & 0xff) << shift2) | |
|
|
((rawReadBuffer [i + 2] & 0xff) << shift3) | |
|
|
((rawReadBuffer [i + 3] & 0xff) << shift4)); |
|
|
if (value < 0x0000ffff) { |
|
|
readBuffer [j++] = (char) value; |
|
|
if (value == (int) '\r') { |
|
|
sawCR = true; |
|
|
} |
|
|
} else if (value < 0x0010ffff) { |
|
|
value -= 0x010000; |
|
|
readBuffer [j++] = (char) (0xd8 | ((value >> 10) & 0x03ff)); |
|
|
readBuffer [j++] = (char) (0xdc | (value & 0x03ff)); |
|
|
} else { |
|
|
encodingError ("UCS-4 value out of range for Unicode", |
|
|
value, i); |
|
|
} |
|
|
} |
|
|
readBufferLength = j; |
|
|
} |
|
5768 |
|
|
5769 |
|
String publicId; |
5770 |
|
String systemId; |
5771 |
|
String baseUri; |
5772 |
|
|
5773 |
/** |
ExternalIdentifiers() |
|
* Report a character encoding error. |
|
|
*/ |
|
|
private void encodingError (String message, int value, int offset) |
|
|
throws SAXException |
|
5774 |
{ |
{ |
|
if (value != -1) |
|
|
message = message + " (character code: 0x" + |
|
|
Integer.toHexString (value) + ')'; |
|
|
error (message); |
|
5775 |
} |
} |
5776 |
|
|
5777 |
|
ExternalIdentifiers(String publicId, String systemId, String baseUri) |
|
////////////////////////////////////////////////////////////////////// |
|
|
// Local Variables. |
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
/** |
|
|
* Re-initialize the variables for each parse. |
|
|
*/ |
|
|
private void initializeVariables () |
|
5778 |
{ |
{ |
5779 |
// First line |
this.publicId = publicId; |
5780 |
line = 1; |
this.systemId = systemId; |
5781 |
column = 0; |
this.baseUri = baseUri; |
|
|
|
|
// Set up the buffers for data and names |
|
|
dataBufferPos = 0; |
|
|
dataBuffer = new char [DATA_BUFFER_INITIAL]; |
|
|
nameBufferPos = 0; |
|
|
nameBuffer = new char [NAME_BUFFER_INITIAL]; |
|
|
|
|
|
// Set up the DTD hash tables |
|
|
elementInfo = new Hashtable (); |
|
|
entityInfo = new Hashtable (); |
|
|
notationInfo = new Hashtable (); |
|
|
skippedPE = false; |
|
|
|
|
|
// Set up the variables for the current |
|
|
// element context. |
|
|
currentElement = null; |
|
|
currentElementContent = CONTENT_UNDECLARED; |
|
|
|
|
|
// Set up the input variables |
|
|
sourceType = INPUT_NONE; |
|
|
inputStack = new Stack (); |
|
|
entityStack = new Stack (); |
|
|
externalEntity = null; |
|
|
tagAttributePos = 0; |
|
|
tagAttributes = new String [100]; |
|
|
rawReadBuffer = new byte [READ_BUFFER_MAX]; |
|
|
readBufferOverflow = -1; |
|
|
|
|
|
scratch = new InputSource (); |
|
|
|
|
|
inLiteral = false; |
|
|
expandPE = false; |
|
|
peIsError = false; |
|
|
|
|
|
doReport = false; |
|
|
|
|
|
inCDATA = false; |
|
|
|
|
|
symbolTable = new Object [SYMBOL_TABLE_LENGTH][]; |
|
5782 |
} |
} |
5783 |
|
|
5784 |
|
} |
5785 |
|
|
5786 |
|
static class EntityInfo |
5787 |
|
{ |
5788 |
|
|
5789 |
// |
int type; |
5790 |
// The current XML handler interface. |
ExternalIdentifiers ids; |
5791 |
// |
String value; |
5792 |
private SAXDriver handler; |
String notationName; |
5793 |
|
|
5794 |
// |
} |
|
// I/O information. |
|
|
// |
|
|
private Reader reader; // current reader |
|
|
private InputStream is; // current input stream |
|
|
private int line; // current line number |
|
|
private int column; // current column number |
|
|
private int sourceType; // type of input source |
|
|
private Stack inputStack; // stack of input soruces |
|
|
private URLConnection externalEntity; // current external entity |
|
|
private int encoding; // current character encoding |
|
|
private int currentByteCount; // bytes read from current source |
|
|
private InputSource scratch; // temporary |
|
|
|
|
|
// |
|
|
// Buffers for decoded but unparsed character input. |
|
|
// |
|
|
private char readBuffer []; |
|
|
private int readBufferPos; |
|
|
private int readBufferLength; |
|
|
private int readBufferOverflow; // overflow from last data chunk. |
|
|
|
|
|
|
|
|
// |
|
|
// Buffer for undecoded raw byte input. |
|
|
// |
|
|
private final static int READ_BUFFER_MAX = 16384; |
|
|
private byte rawReadBuffer []; |
|
|
|
|
|
|
|
|
// |
|
|
// Buffer for attribute values, char refs, DTD stuff. |
|
|
// |
|
|
private static int DATA_BUFFER_INITIAL = 4096; |
|
|
private char dataBuffer []; |
|
|
private int dataBufferPos; |
|
|
|
|
|
// |
|
|
// Buffer for parsed names. |
|
|
// |
|
|
private static int NAME_BUFFER_INITIAL = 1024; |
|
|
private char nameBuffer []; |
|
|
private int nameBufferPos; |
|
|
|
|
|
// |
|
|
// Save any standalone flag |
|
|
// |
|
|
private boolean docIsStandalone; |
|
|
|
|
|
// |
|
|
// Hashtables for DTD information on elements, entities, and notations. |
|
|
// Populated until we start ignoring decls (because of skipping a PE) |
|
|
// |
|
|
private Hashtable elementInfo; |
|
|
private Hashtable entityInfo; |
|
|
private Hashtable notationInfo; |
|
|
private boolean skippedPE; |
|
|
|
|
|
|
|
|
// |
|
|
// Element type currently in force. |
|
|
// |
|
|
private String currentElement; |
|
|
private int currentElementContent; |
|
|
|
|
|
// |
|
|
// Stack of entity names, to detect recursion. |
|
|
// |
|
|
private Stack entityStack; |
|
|
|
|
|
// |
|
|
// PE expansion is enabled in most chunks of the DTD, not all. |
|
|
// When it's enabled, literals are treated differently. |
|
|
// |
|
|
private boolean inLiteral; |
|
|
private boolean expandPE; |
|
|
private boolean peIsError; |
|
|
|
|
|
// |
|
|
// can't report entity expansion inside two constructs: |
|
|
// - attribute expansions (internal entities only) |
|
|
// - markup declarations (parameter entities only) |
|
|
// |
|
|
private boolean doReport; |
|
|
|
|
|
// |
|
|
// Symbol table, for caching interned names. |
|
|
// |
|
|
// These show up wherever XML names or nmtokens are used: naming elements, |
|
|
// attributes, PIs, notations, entities, and enumerated attribute values. |
|
|
// |
|
|
// NOTE: This hashtable doesn't grow. The default size is intended to be |
|
|
// rather large for most documents. Example: one snapshot of the DocBook |
|
|
// XML 4.1 DTD used only about 350 such names. As a rule, only pathological |
|
|
// documents (ones that don't reuse names) should ever see much collision. |
|
|
// |
|
|
// Be sure that SYMBOL_TABLE_LENGTH always stays prime, for best hashing. |
|
|
// "2039" keeps the hash table size at about two memory pages on typical |
|
|
// 32 bit hardware. |
|
|
// |
|
|
private final static int SYMBOL_TABLE_LENGTH = 2039; |
|
|
|
|
|
private Object symbolTable [][]; |
|
5795 |
|
|
5796 |
// |
static class AttributeDecl |
5797 |
// Hash table of attributes found in current start tag. |
{ |
5798 |
// |
|
5799 |
private String tagAttributes []; |
String type; |
5800 |
private int tagAttributePos; |
String value; |
5801 |
|
int valueType; |
5802 |
|
String enumeration; |
5803 |
|
String defaultValue; |
5804 |
|
|
5805 |
// |
} |
|
// Utility flag: have we noticed a CR while reading the last |
|
|
// data chunk? If so, we will have to go back and normalise |
|
|
// CR or CR/LF line ends. |
|
|
// |
|
|
private boolean sawCR; |
|
5806 |
|
|
5807 |
// |
static class ElementDecl |
5808 |
// Utility flag: are we in CDATA? If so, whitespace isn't ignorable. |
{ |
|
// |
|
|
private boolean inCDATA; |
|
5809 |
|
|
5810 |
// |
int contentType; |
5811 |
// Xml version. |
String contentModel; |
5812 |
// |
HashMap attributes; |
5813 |
private static final int XML_10 = 0; |
|
5814 |
private static final int XML_11 = 1; |
} |
5815 |
private int xmlVersion = XML_10; |
|
5816 |
|
static class Input |
5817 |
|
{ |
5818 |
|
|
5819 |
|
int sourceType; |
5820 |
|
URLConnection externalEntity; |
5821 |
|
char[] readBuffer; |
5822 |
|
int readBufferPos; |
5823 |
|
int readBufferLength; |
5824 |
|
int line; |
5825 |
|
int encoding; |
5826 |
|
int readBufferOverflow; |
5827 |
|
InputStream is; |
5828 |
|
int currentByteCount; |
5829 |
|
int column; |
5830 |
|
Reader reader; |
5831 |
|
|
5832 |
|
} |
5833 |
|
|
5834 |
} |
} |
5835 |
|
|