001: package com.sun.portal.rewriter.engines.xml.parser;
002:
003: import java.io.FileNotFoundException;
004: import java.io.IOException;
005: import java.io.InputStream;
006: import java.io.InputStreamReader;
007: import java.io.LineNumberReader;
008: import java.io.PushbackInputStream;
009: import java.io.PushbackReader;
010: import java.io.Reader;
011: import java.io.UnsupportedEncodingException;
012: import java.net.MalformedURLException;
013: import java.net.URL;
014: import java.util.Stack;
015:
016: class StandardXMLReader implements XMLReader {
017: /**
018: * The stack of readers.
019: */
020: private Stack readers;
021:
022: /**
023: * The current push-back reader.
024: */
025: private StackedReader currentReader;
026:
027: public StandardXMLReader(Reader reader) {
028: this .currentReader = new StackedReader();
029: this .readers = new Stack();
030: this .currentReader.lineReader = new LineNumberReader(reader);
031: this .currentReader.pbReader = new PushbackReader(
032: this .currentReader.lineReader, 2);
033: this .currentReader.publicId = "";
034:
035: try {
036: this .currentReader.systemId = new URL("file:.");
037: } catch (MalformedURLException e) {
038: // never happens
039: }
040: }//constructor
041:
042: /**
043: * Scans the encoding from an <?xml...?> tag.
044: *
045: * @param str the first tag in the XML data.
046: *
047: * @return the encoding, or null if no encoding has been specified.
048: */
049: protected String getEncoding(String str) {
050: if (!str.startsWith("<?xml")) {
051: return null;
052: }
053:
054: int index = 5;
055:
056: while (index < str.length()) {
057: StringBuffer key = new StringBuffer();
058:
059: while ((index < str.length()) && (str.charAt(index) <= ' ')) {
060: index++;
061: }
062:
063: while ((index < str.length()) && (str.charAt(index) >= 'a')
064: && (str.charAt(index) <= 'z')) {
065: key.append(str.charAt(index));
066: index++;
067: }
068:
069: while ((index < str.length()) && (str.charAt(index) <= ' ')) {
070: index++;
071: }
072:
073: if ((index >= str.length()) || (str.charAt(index) != '=')) {
074: break;
075: }
076:
077: while ((index < str.length())
078: && (str.charAt(index) != '\'')
079: && (str.charAt(index) != '"')) {
080: index++;
081: }
082:
083: if (index >= str.length()) {
084: break;
085: }
086:
087: char delimiter = str.charAt(index);
088: index++;
089: int index2 = str.indexOf(delimiter, index);
090:
091: if (index2 < 0) {
092: break;
093: }
094:
095: if (key.toString().equals("encoding")) {
096: return str.substring(index, index2);
097: }
098:
099: index = index2 + 1;
100: }
101:
102: return null;
103: }
104:
105: /**
106: * Converts a stream to a reader while detecting the encoding.
107: *
108: * @param stream the input for the XML data.
109: * @param charsRead buffer where to put characters that have been read
110: *
111: * @throws java.io.IOException
112: * if an I/O error occurred
113: */
114: protected Reader stream2reader(InputStream stream,
115: StringBuffer charsRead) throws IOException {
116: PushbackInputStream pbstream = new PushbackInputStream(stream);
117: int b = pbstream.read();
118:
119: switch (b) {
120: case 0x00:
121: case 0xFE:
122: case 0xFF:
123: pbstream.unread(b);
124: return new InputStreamReader(pbstream, "UTF-16");
125:
126: case 0xEF:
127: for (int i = 0; i < 2; i++) {
128: pbstream.read();
129: }
130:
131: return new InputStreamReader(pbstream, "UTF-8");
132:
133: case 0x3C:
134: b = pbstream.read();
135: charsRead.append('<');
136:
137: while ((b > 0) && (b != 0x3E)) {
138: charsRead.append((char) b);
139: b = pbstream.read();
140: }
141:
142: if (b > 0) {
143: charsRead.append((char) b);
144: }
145:
146: String encoding = this .getEncoding(charsRead.toString());
147:
148: if (encoding == null) {
149: return new InputStreamReader(pbstream, "UTF-8");
150: }
151:
152: charsRead.setLength(0);
153:
154: try {
155: return new InputStreamReader(pbstream, encoding);
156: } catch (UnsupportedEncodingException e) {
157: return new InputStreamReader(pbstream, "UTF-8");
158: }
159:
160: default:
161: charsRead.append((char) b);
162: return new InputStreamReader(pbstream, "UTF-8");
163: }
164: }
165:
166: /**
167: * Reads a character.
168: *
169: * @return the character
170: *
171: * @throws java.io.IOException
172: * if no character could be read
173: */
174: public char read() throws IOException {
175: int ch = this .currentReader.pbReader.read();
176:
177: while (ch < 0) {
178: if (this .readers.empty()) {
179: throw new IOException("Unexpected EOF");
180: }
181:
182: this .currentReader.pbReader.close();
183: this .currentReader = (StackedReader) this .readers.pop();
184: ch = this .currentReader.pbReader.read();
185: }
186:
187: return (char) ch;
188: }
189:
190: /**
191: * Returns true if the current stream has no more characters left to be
192: * read.
193: *
194: * @throws java.io.IOException
195: * if an I/O error occurred
196: */
197: public boolean atEOFOfCurrentStream() throws IOException {
198: int ch = this .currentReader.pbReader.read();
199:
200: if (ch < 0) {
201: return true;
202: } else {
203: this .currentReader.pbReader.unread(ch);
204: return false;
205: }
206: }
207:
208: /**
209: * Returns true if there are no more characters left to be read.
210: *
211: * @throws java.io.IOException
212: * if an I/O error occurred
213: */
214: public boolean atEOF() throws IOException {
215: int ch = this .currentReader.pbReader.read();
216:
217: while (ch < 0) {
218: if (this .readers.empty()) {
219: return true;
220: }
221:
222: this .currentReader.pbReader.close();
223: this .currentReader = (StackedReader) this .readers.pop();
224: ch = this .currentReader.pbReader.read();
225: }
226:
227: this .currentReader.pbReader.unread(ch);
228: return false;
229: }
230:
231: /**
232: * Pushes the last character read back to the stream.
233: *
234: * @param ch the character to push back.
235: *
236: * @throws java.io.IOException
237: * if an I/O error occurred
238: */
239: public void unread(char ch) throws IOException {
240: this .currentReader.pbReader.unread(ch);
241: }
242:
243: /**
244: * Opens a stream from a public and system ID.
245: *
246: * @param publicID the public ID, which may be null
247: * @param systemID the system ID, which is never null
248: *
249: * @throws java.net.MalformedURLException
250: * if the system ID does not contain a valid URL
251: * @throws java.io.FileNotFoundException
252: * if the system ID refers to a local file which does not exist
253: * @throws java.io.IOException
254: * if an error occurred opening the stream
255: */
256: public Reader openStream(String publicID, String systemID)
257: throws MalformedURLException, FileNotFoundException,
258: IOException {
259: URL url = new URL(this .currentReader.systemId, systemID);
260:
261: if (url.getRef() != null) {
262: String ref = url.getRef();
263:
264: if (url.getFile().length() > 0) {
265: url = new URL(url.getProtocol(), url.getHost(), url
266: .getPort(), url.getFile());
267: url = new URL("jar:" + url + '!' + ref);
268: } else {
269: url = StandardXMLReader.class.getResource(ref);
270: }
271: }
272:
273: this .currentReader.publicId = publicID;
274: this .currentReader.systemId = url;
275: StringBuffer charsRead = new StringBuffer();
276: Reader reader = this .stream2reader(url.openStream(), charsRead);
277:
278: if (charsRead.length() == 0) {
279: return reader;
280: }
281:
282: String charsReadStr = charsRead.toString();
283: PushbackReader pbreader = new PushbackReader(reader,
284: charsReadStr.length());
285:
286: for (int i = charsReadStr.length() - 1; i >= 0; i--) {
287: pbreader.unread(charsReadStr.charAt(i));
288: }
289:
290: return pbreader;
291: }
292:
293: /**
294: * Starts a new stream from a Java reader. The new stream is used
295: * temporary to read data from. If that stream is exhausted, control
296: * returns to the parent stream.
297: *
298: * @param reader the non-null reader to read the new data from
299: */
300: public void startNewStream(Reader reader) {
301: this .startNewStream(reader, false);
302: }
303:
304: /**
305: * Starts a new stream from a Java reader. The new stream is used
306: * temporary to read data from. If that stream is exhausted, control
307: * returns to the parent stream.
308: *
309: * @param reader the non-null reader to read the new data from
310: * @param isInternalEntity true if the reader is produced by resolving
311: * an internal entity
312: */
313: public void startNewStream(Reader reader, boolean isInternalEntity) {
314: StackedReader oldReader = this .currentReader;
315: this .readers.push(this .currentReader);
316: this .currentReader = new StackedReader();
317:
318: if (isInternalEntity) {
319: this .currentReader.lineReader = null;
320: this .currentReader.pbReader = new PushbackReader(reader, 2);
321: } else {
322: this .currentReader.lineReader = new LineNumberReader(reader);
323: this .currentReader.pbReader = new PushbackReader(
324: this .currentReader.lineReader, 2);
325: }
326:
327: this .currentReader.systemId = oldReader.systemId;
328: this .currentReader.publicId = oldReader.publicId;
329: }
330:
331: /**
332: * Returns the current "level" of the stream on the stack of streams.
333: */
334: public int getStreamLevel() {
335: return this .readers.size();
336: }
337:
338: /**
339: * Returns the line number of the data in the current stream.
340: */
341: public int getLineNr() {
342: if (this .currentReader.lineReader == null) {
343: StackedReader sr = (StackedReader) this .readers.peek();
344:
345: if (sr.lineReader == null) {
346: return 0;
347: } else {
348: return sr.lineReader.getLineNumber() + 1;
349: }
350: }
351:
352: return this .currentReader.lineReader.getLineNumber() + 1;
353: }
354:
355: /**
356: * Sets the system ID of the current stream.
357: *
358: * @param systemID the system ID
359: *
360: * @throws java.net.MalformedURLException
361: * if the system ID does not contain a valid URL
362: */
363: public void setSystemID(String systemID)
364: throws MalformedURLException {
365: this .currentReader.systemId = new URL(
366: this .currentReader.systemId, systemID);
367: }
368:
369: /**
370: * Sets the public ID of the current stream.
371: *
372: * @param publicID the public ID
373: */
374: public void setPublicID(String publicID) {
375: this .currentReader.publicId = publicID;
376: }
377:
378: /**
379: * Returns the current system ID.
380: */
381: public String getSystemID() {
382: return this .currentReader.systemId.toString();
383: }
384:
385: /**
386: * A stacked reader.
387: */
388: private class StackedReader {
389: PushbackReader pbReader;
390:
391: LineNumberReader lineReader;
392:
393: URL systemId;
394:
395: String publicId;
396: }
397: }//class StandardXMLReader
|