root / trunk / build / distribution / IzPack / src / lib / net / n3 / nanoxml / StdXMLReader.java @ 21757
History | View | Annotate | Download (17.3 KB)
1 |
/* StdXMLReader.java NanoXML/Java
|
---|---|
2 |
*
|
3 |
* $Revision: 5819 $
|
4 |
* $Date: 2006-06-14 09:29:09 +0200 $
|
5 |
* $Name$
|
6 |
*
|
7 |
* This file is part of NanoXML 2 for Java.
|
8 |
* Copyright (C) 2001 Marc De Scheemaecker, All Rights Reserved.
|
9 |
*
|
10 |
* This software is provided 'as-is', without any express or implied warranty.
|
11 |
* In no event will the authors be held liable for any damages arising from the
|
12 |
* use of this software.
|
13 |
*
|
14 |
* Permission is granted to anyone to use this software for any purpose,
|
15 |
* including commercial applications, and to alter it and redistribute it
|
16 |
* freely, subject to the following restrictions:
|
17 |
*
|
18 |
* 1. The origin of this software must not be misrepresented; you must not
|
19 |
* claim that you wrote the original software. If you use this software in
|
20 |
* a product, an acknowledgment in the product documentation would be
|
21 |
* appreciated but is not required.
|
22 |
*
|
23 |
* 2. Altered source versions must be plainly marked as such, and must not be
|
24 |
* misrepresented as being the original software.
|
25 |
*
|
26 |
* 3. This notice may not be removed or altered from any source distribution.
|
27 |
*/
|
28 |
|
29 |
package net.n3.nanoxml; |
30 |
|
31 |
|
32 |
import java.io.FileInputStream; |
33 |
import java.io.FileNotFoundException; |
34 |
import java.io.IOException; |
35 |
import java.io.InputStream; |
36 |
import java.io.InputStreamReader; |
37 |
import java.io.LineNumberReader; |
38 |
import java.io.PushbackInputStream; |
39 |
import java.io.PushbackReader; |
40 |
import java.io.Reader; |
41 |
import java.io.StringReader; |
42 |
import java.io.UnsupportedEncodingException; |
43 |
import java.net.MalformedURLException; |
44 |
import java.net.URL; |
45 |
import java.util.Stack; |
46 |
|
47 |
|
48 |
/**
|
49 |
* StdXMLReader reads the data to be parsed.
|
50 |
*
|
51 |
* @author Marc De Scheemaecker
|
52 |
* @version $Name$, $Revision: 5819 $
|
53 |
*/
|
54 |
public class StdXMLReader |
55 |
implements IXMLReader
|
56 |
{ |
57 |
|
58 |
/**
|
59 |
* The stack of push-back readers.
|
60 |
*/
|
61 |
private Stack pbreaders; |
62 |
|
63 |
|
64 |
/**
|
65 |
* The stack of line-number readers.
|
66 |
*/
|
67 |
private Stack linereaders; |
68 |
|
69 |
|
70 |
/**
|
71 |
* The stack of system ids.
|
72 |
*/
|
73 |
private Stack systemIds; |
74 |
|
75 |
|
76 |
/**
|
77 |
* The stack of public ids.
|
78 |
*/
|
79 |
private Stack publicIds; |
80 |
|
81 |
|
82 |
/**
|
83 |
* The current push-back reader.
|
84 |
*/
|
85 |
private PushbackReader currentPbReader; |
86 |
|
87 |
|
88 |
/**
|
89 |
* The current line-number reader.
|
90 |
*/
|
91 |
private LineNumberReader currentLineReader; |
92 |
|
93 |
|
94 |
/**
|
95 |
* The current system ID.
|
96 |
*/
|
97 |
private URL currentSystemID; |
98 |
|
99 |
|
100 |
/**
|
101 |
* The current public ID.
|
102 |
*/
|
103 |
private String currentPublicID; |
104 |
|
105 |
|
106 |
/**
|
107 |
* Creates a new reader using a string as input.
|
108 |
*
|
109 |
* @param str the string containing the XML data
|
110 |
*/
|
111 |
public static IXMLReader stringReader(String str) |
112 |
{ |
113 |
return new StdXMLReader(new StringReader(str)); |
114 |
} |
115 |
|
116 |
|
117 |
/**
|
118 |
* Creates a new reader using a file as input.
|
119 |
*
|
120 |
* @param filename the name of the file containing the XML data
|
121 |
*
|
122 |
* @throws java.io.FileNotFoundException
|
123 |
* if the file could not be found
|
124 |
* @throws java.io.IOException
|
125 |
* if an I/O error occurred
|
126 |
*/
|
127 |
public static IXMLReader fileReader(String filename) |
128 |
throws FileNotFoundException, |
129 |
IOException
|
130 |
{ |
131 |
IXMLReader reader = new StdXMLReader(new FileInputStream(filename)); |
132 |
reader.setSystemID(filename); |
133 |
return reader;
|
134 |
} |
135 |
|
136 |
|
137 |
/**
|
138 |
* Initializes the reader from a system and public ID.
|
139 |
*
|
140 |
* @param publicID the public ID which may be null.
|
141 |
* @param systemID the non-null system ID.
|
142 |
*
|
143 |
* @throws MalformedURLException
|
144 |
* if the system ID does not contain a valid URL
|
145 |
* @throws FileNotFoundException
|
146 |
* if the system ID refers to a local file which does not exist
|
147 |
* @throws IOException
|
148 |
* if an error occurred opening the stream
|
149 |
*/
|
150 |
public StdXMLReader(String publicID, |
151 |
String systemID)
|
152 |
throws MalformedURLException, |
153 |
FileNotFoundException,
|
154 |
IOException
|
155 |
{ |
156 |
URL systemIDasURL = null; |
157 |
|
158 |
try {
|
159 |
systemIDasURL = new URL(systemID); |
160 |
} catch (MalformedURLException e) { |
161 |
systemID = "file:" + systemID;
|
162 |
|
163 |
try {
|
164 |
systemIDasURL = new URL(systemID); |
165 |
} catch (MalformedURLException e2) { |
166 |
throw e;
|
167 |
} |
168 |
} |
169 |
|
170 |
Reader reader = this.openStream(publicID, systemIDasURL.toString()); |
171 |
this.currentLineReader = new LineNumberReader(reader); |
172 |
this.currentPbReader = new PushbackReader(this.currentLineReader, 2); |
173 |
this.pbreaders = new Stack(); |
174 |
this.linereaders = new Stack(); |
175 |
this.publicIds = new Stack(); |
176 |
this.systemIds = new Stack(); |
177 |
this.currentPublicID = publicID;
|
178 |
this.currentSystemID = systemIDasURL;
|
179 |
} |
180 |
|
181 |
|
182 |
/**
|
183 |
* Initializes the XML reader.
|
184 |
*
|
185 |
* @param reader the input for the XML data.
|
186 |
*/
|
187 |
public StdXMLReader(Reader reader) |
188 |
{ |
189 |
this.currentLineReader = new LineNumberReader(reader); |
190 |
this.currentPbReader = new PushbackReader(this.currentLineReader, 2); |
191 |
this.pbreaders = new Stack(); |
192 |
this.linereaders = new Stack(); |
193 |
this.publicIds = new Stack(); |
194 |
this.systemIds = new Stack(); |
195 |
this.currentPublicID = ""; |
196 |
|
197 |
try {
|
198 |
this.currentSystemID = new URL("file:."); |
199 |
} catch (MalformedURLException e) { |
200 |
// never happens
|
201 |
} |
202 |
} |
203 |
|
204 |
|
205 |
/**
|
206 |
* Cleans up the object when it's destroyed.
|
207 |
*/
|
208 |
protected void finalize() |
209 |
throws Throwable |
210 |
{ |
211 |
this.currentLineReader = null; |
212 |
this.currentPbReader = null; |
213 |
this.pbreaders.clear();
|
214 |
this.pbreaders = null; |
215 |
this.linereaders.clear();
|
216 |
this.linereaders = null; |
217 |
this.publicIds.clear();
|
218 |
this.publicIds = null; |
219 |
this.systemIds.clear();
|
220 |
this.systemIds = null; |
221 |
this.currentPublicID = null; |
222 |
super.finalize();
|
223 |
} |
224 |
|
225 |
|
226 |
/**
|
227 |
* Scans the encoding from an <?xml?> tag.
|
228 |
*
|
229 |
* @param str the first tag in the XML data.
|
230 |
*
|
231 |
* @return the encoding, or null if no encoding has been specified.
|
232 |
*/
|
233 |
protected String getEncoding(String str) |
234 |
{ |
235 |
if (! str.startsWith("<?xml")) { |
236 |
return null; |
237 |
} |
238 |
|
239 |
int index = 5; |
240 |
|
241 |
while (index < str.length()) {
|
242 |
StringBuffer key = new StringBuffer(); |
243 |
|
244 |
while ((index < str.length()) && (str.charAt(index) <= ' ')) { |
245 |
index++; |
246 |
} |
247 |
|
248 |
while ((index < str.length())
|
249 |
&& (str.charAt(index) >= 'a')
|
250 |
&& (str.charAt(index) <= 'z')) {
|
251 |
key.append(str.charAt(index)); |
252 |
index++; |
253 |
} |
254 |
|
255 |
while ((index < str.length()) && (str.charAt(index) <= ' ')) { |
256 |
index++; |
257 |
} |
258 |
|
259 |
if ((index >= str.length()) || (str.charAt(index) != '=')) { |
260 |
break;
|
261 |
} |
262 |
|
263 |
while ((index < str.length()) && (str.charAt(index) != '\'') |
264 |
&& (str.charAt(index) != '"')) {
|
265 |
index++; |
266 |
} |
267 |
|
268 |
if (index >= str.length()) {
|
269 |
break;
|
270 |
} |
271 |
|
272 |
char delimiter = str.charAt(index);
|
273 |
index++; |
274 |
int index2 = str.indexOf(delimiter, index);
|
275 |
|
276 |
if (index2 < 0) { |
277 |
break;
|
278 |
} |
279 |
|
280 |
if (key.toString().equals("encoding")) { |
281 |
return str.substring(index, index2);
|
282 |
} |
283 |
|
284 |
index = index2 + 1;
|
285 |
} |
286 |
|
287 |
return null; |
288 |
} |
289 |
|
290 |
|
291 |
/**
|
292 |
* Converts a stream to a reader while detecting the encoding.
|
293 |
*
|
294 |
* @param stream the input for the XML data.
|
295 |
* @param charsRead buffer where to put characters that have been read
|
296 |
*
|
297 |
* @throws java.io.IOException
|
298 |
* if an I/O error occurred
|
299 |
*/
|
300 |
protected Reader stream2reader(InputStream stream, |
301 |
StringBuffer charsRead)
|
302 |
throws IOException |
303 |
{ |
304 |
PushbackInputStream pbstream = new PushbackInputStream(stream); |
305 |
int b = pbstream.read();
|
306 |
|
307 |
switch (b) {
|
308 |
case 0x00: |
309 |
case 0xFE: |
310 |
case 0xFF: |
311 |
pbstream.unread(b); |
312 |
return new InputStreamReader(pbstream, "UTF-16"); |
313 |
|
314 |
case 0xEF: |
315 |
for (int i = 0; i < 2; i++) { |
316 |
pbstream.read(); |
317 |
} |
318 |
|
319 |
return new InputStreamReader(pbstream, "UTF-8"); |
320 |
|
321 |
case 0x3C: |
322 |
b = pbstream.read(); |
323 |
charsRead.append('<');
|
324 |
|
325 |
while ((b > 0) && (b != 0x3E)) { |
326 |
charsRead.append((char) b);
|
327 |
b = pbstream.read(); |
328 |
} |
329 |
|
330 |
if (b > 0) { |
331 |
charsRead.append((char) b);
|
332 |
} |
333 |
|
334 |
String encoding = this.getEncoding(charsRead.toString()); |
335 |
|
336 |
if (encoding == null) { |
337 |
return new InputStreamReader(pbstream, "UTF-8"); |
338 |
} |
339 |
|
340 |
charsRead.setLength(0);
|
341 |
|
342 |
try {
|
343 |
return new InputStreamReader(pbstream, encoding); |
344 |
} catch (UnsupportedEncodingException e) { |
345 |
return new InputStreamReader(pbstream, "UTF-8"); |
346 |
} |
347 |
|
348 |
default:
|
349 |
charsRead.append((char) b);
|
350 |
return new InputStreamReader(pbstream, "UTF-8"); |
351 |
} |
352 |
} |
353 |
|
354 |
|
355 |
/**
|
356 |
* Initializes the XML reader.
|
357 |
*
|
358 |
* @param stream the input for the XML data.
|
359 |
*
|
360 |
* @throws java.io.IOException
|
361 |
* if an I/O error occurred
|
362 |
*/
|
363 |
public StdXMLReader(InputStream stream) |
364 |
throws IOException |
365 |
{ |
366 |
PushbackInputStream pbstream = new PushbackInputStream(stream); |
367 |
StringBuffer charsRead = new StringBuffer(); |
368 |
Reader reader = this.stream2reader(stream, charsRead); |
369 |
this.currentLineReader = new LineNumberReader(reader); |
370 |
this.currentPbReader = new PushbackReader(this.currentLineReader, 2); |
371 |
this.pbreaders = new Stack(); |
372 |
this.linereaders = new Stack(); |
373 |
this.publicIds = new Stack(); |
374 |
this.systemIds = new Stack(); |
375 |
this.currentPublicID = ""; |
376 |
|
377 |
try {
|
378 |
this.currentSystemID = new URL("file:."); |
379 |
} catch (MalformedURLException e) { |
380 |
// never happens
|
381 |
} |
382 |
|
383 |
this.startNewStream(new StringReader(charsRead.toString())); |
384 |
} |
385 |
|
386 |
|
387 |
/**
|
388 |
* Reads a character.
|
389 |
*
|
390 |
* @return the character
|
391 |
*
|
392 |
* @throws java.io.IOException
|
393 |
* if no character could be read
|
394 |
*/
|
395 |
public char read() |
396 |
throws IOException |
397 |
{ |
398 |
int ch = this.currentPbReader.read(); |
399 |
|
400 |
while (ch < 0) { |
401 |
if (this.pbreaders.empty()) { |
402 |
throw new IOException("Unexpected EOF"); |
403 |
} |
404 |
|
405 |
this.currentPbReader.close();
|
406 |
this.currentPbReader = (PushbackReader) this.pbreaders.pop(); |
407 |
this.currentLineReader = (LineNumberReader) this.linereaders.pop(); |
408 |
this.currentSystemID = (URL) this.systemIds.pop(); |
409 |
this.currentPublicID = (String) this.publicIds.pop(); |
410 |
ch = this.currentPbReader.read();
|
411 |
} |
412 |
|
413 |
if (ch == 0x0D) { // CR |
414 |
// using recursion could convert "\r\r\n" to "\n" (wrong),
|
415 |
// newline combo "\r\n" isn't normalized if it spans streams
|
416 |
// next 'read()' will pop pbreaders stack appropriately
|
417 |
ch = this.currentPbReader.read();
|
418 |
|
419 |
if (ch != 0x0A && ch > 0) { // LF |
420 |
this.currentPbReader.unread(ch);
|
421 |
} |
422 |
return (char) 0x0A; // normalized: always LF |
423 |
} |
424 |
|
425 |
return (char) ch; |
426 |
} |
427 |
|
428 |
|
429 |
/**
|
430 |
* Returns true if the current stream has no more characters left to be
|
431 |
* read.
|
432 |
*
|
433 |
* @throws java.io.IOException
|
434 |
* if an I/O error occurred
|
435 |
*/
|
436 |
public boolean atEOFOfCurrentStream() |
437 |
throws IOException |
438 |
{ |
439 |
int ch = this.currentPbReader.read(); |
440 |
|
441 |
if (ch < 0) { |
442 |
return true; |
443 |
} else {
|
444 |
this.currentPbReader.unread(ch);
|
445 |
return false; |
446 |
} |
447 |
} |
448 |
|
449 |
|
450 |
/**
|
451 |
* Returns true if there are no more characters left to be read.
|
452 |
*
|
453 |
* @throws java.io.IOException
|
454 |
* if an I/O error occurred
|
455 |
*/
|
456 |
public boolean atEOF() |
457 |
throws IOException |
458 |
{ |
459 |
int ch = this.currentPbReader.read(); |
460 |
|
461 |
while (ch < 0) { |
462 |
if (this.pbreaders.empty()) { |
463 |
return true; |
464 |
} |
465 |
|
466 |
this.currentPbReader.close();
|
467 |
this.currentPbReader = (PushbackReader) this.pbreaders.pop(); |
468 |
this.currentLineReader = (LineNumberReader) this.linereaders.pop(); |
469 |
this.currentSystemID = (URL) this.systemIds.pop(); |
470 |
this.currentPublicID = (String) this.publicIds.pop(); |
471 |
ch = this.currentPbReader.read();
|
472 |
} |
473 |
|
474 |
this.currentPbReader.unread(ch);
|
475 |
return false; |
476 |
} |
477 |
|
478 |
|
479 |
/**
|
480 |
* Pushes the last character read back to the stream.
|
481 |
*
|
482 |
* @throws java.io.IOException
|
483 |
* if an I/O error occurred
|
484 |
*/
|
485 |
public void unread(char ch) |
486 |
throws IOException |
487 |
{ |
488 |
this.currentPbReader.unread(ch);
|
489 |
} |
490 |
|
491 |
|
492 |
/**
|
493 |
* Opens a stream from a public and system ID.
|
494 |
*
|
495 |
* @param publicID the public ID, which may be null
|
496 |
* @param systemID the system ID, which is never null
|
497 |
*
|
498 |
* @throws java.net.MalformedURLException
|
499 |
* if the system ID does not contain a valid URL
|
500 |
* @throws java.io.FileNotFoundException
|
501 |
* if the system ID refers to a local file which does not exist
|
502 |
* @throws java.io.IOException
|
503 |
* if an error occurred opening the stream
|
504 |
*/
|
505 |
public Reader openStream(String publicID, |
506 |
String systemID)
|
507 |
throws MalformedURLException, |
508 |
FileNotFoundException,
|
509 |
IOException
|
510 |
{ |
511 |
URL url = new URL(this.currentSystemID, systemID); |
512 |
StringBuffer charsRead = new StringBuffer(); |
513 |
Reader reader = this.stream2reader(url.openStream(), charsRead); |
514 |
|
515 |
if (charsRead.length() == 0) { |
516 |
return reader;
|
517 |
} |
518 |
|
519 |
String charsReadStr = charsRead.toString();
|
520 |
PushbackReader pbreader = new PushbackReader(reader, |
521 |
charsReadStr.length()); |
522 |
for (int i = charsReadStr.length() - 1; i >= 0; i--) { |
523 |
pbreader.unread(charsReadStr.charAt(i)); |
524 |
} |
525 |
|
526 |
return pbreader;
|
527 |
} |
528 |
|
529 |
|
530 |
/**
|
531 |
* Starts a new stream from a Java reader. The new stream is used
|
532 |
* temporary to read data from. If that stream is exhausted, control
|
533 |
* returns to the parent stream.
|
534 |
*
|
535 |
* @param reader the non-null reader to read the new data from
|
536 |
*/
|
537 |
public void startNewStream(Reader reader) |
538 |
{ |
539 |
this.pbreaders.push(this.currentPbReader); |
540 |
this.linereaders.push(this.currentLineReader); |
541 |
this.systemIds.push(this.currentSystemID); |
542 |
this.publicIds.push(this.currentPublicID); |
543 |
this.currentLineReader = new LineNumberReader(reader); |
544 |
this.currentPbReader = new PushbackReader(this.currentLineReader, 2); |
545 |
} |
546 |
|
547 |
|
548 |
/**
|
549 |
* Returns the line number of the data in the current stream.
|
550 |
*/
|
551 |
public int getLineNr() |
552 |
{ |
553 |
return this.currentLineReader.getLineNumber() + 1; |
554 |
} |
555 |
|
556 |
|
557 |
/**
|
558 |
* Sets the system ID of the current stream.
|
559 |
*
|
560 |
* @param systemID the system ID
|
561 |
*
|
562 |
* @throws java.net.MalformedURLException
|
563 |
* if the system ID does not contain a valid URL
|
564 |
*/
|
565 |
public void setSystemID(String systemID) |
566 |
throws MalformedURLException |
567 |
{ |
568 |
this.currentSystemID = new URL(this.currentSystemID, systemID); |
569 |
} |
570 |
|
571 |
|
572 |
/**
|
573 |
* Sets the public ID of the current stream.
|
574 |
*
|
575 |
* @param publicID the public ID
|
576 |
*/
|
577 |
public void setPublicID(String publicID) |
578 |
{ |
579 |
this.currentPublicID = publicID;
|
580 |
} |
581 |
|
582 |
|
583 |
/**
|
584 |
* Returns the current system ID.
|
585 |
*/
|
586 |
public String getSystemID() |
587 |
{ |
588 |
return this.currentSystemID.toString(); |
589 |
} |
590 |
|
591 |
|
592 |
/**
|
593 |
* Returns the current public ID.
|
594 |
*/
|
595 |
public String getPublicID() |
596 |
{ |
597 |
return this.currentPublicID; |
598 |
} |
599 |
|
600 |
} |