| /* StdXMLReader.java NanoXML/Java |
| * |
| * $Revision: 1.4 $ |
| * $Date: 2002/01/04 21:03:28 $ |
| * $Name: RELEASE_2_2_1 $ |
| * |
| * This file is part of NanoXML 2 for Java. |
| * Copyright (C) 2000-2002 Marc De Scheemaecker, All Rights Reserved. |
| * |
| * This software is provided 'as-is', without any express or implied warranty. |
| * In no event will the authors be held liable for any damages arising from the |
| * use of this software. |
| * |
| * Permission is granted to anyone to use this software for any purpose, |
| * including commercial applications, and to alter it and redistribute it |
| * freely, subject to the following restrictions: |
| * |
| * 1. The origin of this software must not be misrepresented; you must not |
| * claim that you wrote the original software. If you use this software in |
| * a product, an acknowledgment in the product documentation would be |
| * appreciated but is not required. |
| * |
| * 2. Altered source versions must be plainly marked as such, and must not be |
| * misrepresented as being the original software. |
| * |
| * 3. This notice may not be removed or altered from any source distribution. |
| */ |
| |
| package processing.xml; |
| |
| |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.IOException; |
| //import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.LineNumberReader; |
| import java.io.PushbackReader; |
| import java.io.PushbackInputStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.io.UnsupportedEncodingException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.Stack; |
| |
| |
| /** |
| * StdXMLReader reads the data to be parsed. |
| * |
| * @author Marc De Scheemaecker |
| * @version $Name: RELEASE_2_2_1 $, $Revision: 1.4 $ |
| */ |
| public class StdXMLReader |
| { |
| |
| /** |
| * A stacked reader. |
| * |
| * @author Marc De Scheemaecker |
| * @version $Name: RELEASE_2_2_1 $, $Revision: 1.4 $ |
| */ |
| private class StackedReader |
| { |
| |
| PushbackReader pbReader; |
| |
| LineNumberReader lineReader; |
| |
| URL systemId; |
| |
| String publicId; |
| |
| } |
| |
| |
| /** |
| * The stack of readers. |
| */ |
| private Stack<StackedReader> readers; |
| |
| |
| /** |
| * The current push-back reader. |
| */ |
| private StackedReader currentReader; |
| |
| |
| /** |
| * Creates a new reader using a string as input. |
| * |
| * @param str the string containing the XML data |
| */ |
| public static StdXMLReader stringReader(String str) |
| { |
| return new StdXMLReader(new StringReader(str)); |
| } |
| |
| |
| /** |
| * Creates a new reader using a file as input. |
| * |
| * @param filename the name of the file containing the XML data |
| * |
| * @throws java.io.FileNotFoundException |
| * if the file could not be found |
| * @throws java.io.IOException |
| * if an I/O error occurred |
| */ |
| public static StdXMLReader fileReader(String filename) |
| throws FileNotFoundException, |
| IOException |
| { |
| StdXMLReader r = new StdXMLReader(new FileInputStream(filename)); |
| r.setSystemID(filename); |
| |
| for (int i = 0; i < r.readers.size(); i++) { |
| StackedReader sr = (StackedReader) r.readers.elementAt(i); |
| sr.systemId = r.currentReader.systemId; |
| } |
| |
| return r; |
| } |
| |
| |
| /** |
| * Initializes the reader from a system and public ID. |
| * |
| * @param publicID the public ID which may be null. |
| * @param systemID the non-null system ID. |
| * |
| * @throws MalformedURLException |
| * if the system ID does not contain a valid URL |
| * @throws FileNotFoundException |
| * if the system ID refers to a local file which does not exist |
| * @throws IOException |
| * if an error occurred opening the stream |
| */ |
| public StdXMLReader(String publicID, |
| String systemID) |
| throws MalformedURLException, |
| FileNotFoundException, |
| IOException |
| { |
| URL systemIDasURL = null; |
| |
| try { |
| systemIDasURL = new URL(systemID); |
| } catch (MalformedURLException e) { |
| systemID = "file:" + systemID; |
| |
| try { |
| systemIDasURL = new URL(systemID); |
| } catch (MalformedURLException e2) { |
| throw e; |
| } |
| } |
| |
| this.currentReader = new StackedReader(); |
| this.readers = new Stack<StackedReader>(); |
| Reader reader = this.openStream(publicID, systemIDasURL.toString()); |
| this.currentReader.lineReader = new LineNumberReader(reader); |
| this.currentReader.pbReader |
| = new PushbackReader(this.currentReader.lineReader, 2); |
| } |
| |
| |
| /** |
| * Initializes the XML reader. |
| * |
| * @param reader the input for the XML data. |
| */ |
| public StdXMLReader(Reader reader) |
| { |
| this.currentReader = new StackedReader(); |
| this.readers = new Stack<StackedReader>(); |
| this.currentReader.lineReader = new LineNumberReader(reader); |
| this.currentReader.pbReader |
| = new PushbackReader(this.currentReader.lineReader, 2); |
| this.currentReader.publicId = ""; |
| |
| try { |
| this.currentReader.systemId = new URL("file:."); |
| } catch (MalformedURLException e) { |
| // never happens |
| } |
| } |
| |
| |
| /** |
| * Cleans up the object when it's destroyed. |
| */ |
| protected void finalize() |
| throws Throwable |
| { |
| this.currentReader.lineReader = null; |
| this.currentReader.pbReader = null; |
| this.currentReader.systemId = null; |
| this.currentReader.publicId = null; |
| this.currentReader = null; |
| this.readers.clear(); |
| super.finalize(); |
| } |
| |
| |
| /** |
| * Scans the encoding from an <?xml...?> tag. |
| * |
| * @param str the first tag in the XML data. |
| * |
| * @return the encoding, or null if no encoding has been specified. |
| */ |
| protected String getEncoding(String str) |
| { |
| if (! str.startsWith("<?xml")) { |
| return null; |
| } |
| |
| int index = 5; |
| |
| while (index < str.length()) { |
| StringBuffer key = new StringBuffer(); |
| |
| while ((index < str.length()) && (str.charAt(index) <= ' ')) { |
| index++; |
| } |
| |
| while ((index < str.length()) |
| && (str.charAt(index) >= 'a') |
| && (str.charAt(index) <= 'z')) { |
| key.append(str.charAt(index)); |
| index++; |
| } |
| |
| while ((index < str.length()) && (str.charAt(index) <= ' ')) { |
| index++; |
| } |
| |
| if ((index >= str.length()) || (str.charAt(index) != '=')) { |
| break; |
| } |
| |
| while ((index < str.length()) && (str.charAt(index) != '\'') |
| && (str.charAt(index) != '"')) { |
| index++; |
| } |
| |
| if (index >= str.length()) { |
| break; |
| } |
| |
| char delimiter = str.charAt(index); |
| index++; |
| int index2 = str.indexOf(delimiter, index); |
| |
| if (index2 < 0) { |
| break; |
| } |
| |
| if (key.toString().equals("encoding")) { |
| return str.substring(index, index2); |
| } |
| |
| index = index2 + 1; |
| } |
| |
| return null; |
| } |
| |
| |
| /** |
| * Converts a stream to a reader while detecting the encoding. |
| * |
| * @param stream the input for the XML data. |
| * @param charsRead buffer where to put characters that have been read |
| * |
| * @throws java.io.IOException |
| * if an I/O error occurred |
| */ |
| protected Reader stream2reader(InputStream stream, |
| StringBuffer charsRead) |
| throws IOException |
| { |
| PushbackInputStream pbstream = new PushbackInputStream(stream); |
| int b = pbstream.read(); |
| |
| switch (b) { |
| case 0x00: |
| case 0xFE: |
| case 0xFF: |
| pbstream.unread(b); |
| return new InputStreamReader(pbstream, "UTF-16"); |
| |
| case 0xEF: |
| for (int i = 0; i < 2; i++) { |
| pbstream.read(); |
| } |
| |
| return new InputStreamReader(pbstream, "UTF-8"); |
| |
| case 0x3C: |
| b = pbstream.read(); |
| charsRead.append('<'); |
| |
| while ((b > 0) && (b != 0x3E)) { |
| charsRead.append((char) b); |
| b = pbstream.read(); |
| } |
| |
| if (b > 0) { |
| charsRead.append((char) b); |
| } |
| |
| String encoding = this.getEncoding(charsRead.toString()); |
| |
| if (encoding == null) { |
| return new InputStreamReader(pbstream, "UTF-8"); |
| } |
| |
| charsRead.setLength(0); |
| |
| try { |
| return new InputStreamReader(pbstream, encoding); |
| } catch (UnsupportedEncodingException e) { |
| return new InputStreamReader(pbstream, "UTF-8"); |
| } |
| |
| default: |
| charsRead.append((char) b); |
| return new InputStreamReader(pbstream, "UTF-8"); |
| } |
| } |
| |
| |
| /** |
| * Initializes the XML reader. |
| * |
| * @param stream the input for the XML data. |
| * |
| * @throws java.io.IOException |
| * if an I/O error occurred |
| */ |
| public StdXMLReader(InputStream stream) |
| throws IOException |
| { |
| // unused? |
| //PushbackInputStream pbstream = new PushbackInputStream(stream); |
| StringBuffer charsRead = new StringBuffer(); |
| Reader reader = this.stream2reader(stream, charsRead); |
| this.currentReader = new StackedReader(); |
| this.readers = new Stack<StackedReader>(); |
| this.currentReader.lineReader = new LineNumberReader(reader); |
| this.currentReader.pbReader |
| = new PushbackReader(this.currentReader.lineReader, 2); |
| this.currentReader.publicId = ""; |
| |
| try { |
| this.currentReader.systemId = new URL("file:."); |
| } catch (MalformedURLException e) { |
| // never happens |
| } |
| |
| this.startNewStream(new StringReader(charsRead.toString())); |
| } |
| |
| |
| /** |
| * Reads a character. |
| * |
| * @return the character |
| * |
| * @throws java.io.IOException |
| * if no character could be read |
| */ |
| public char read() |
| throws IOException |
| { |
| int ch = this.currentReader.pbReader.read(); |
| |
| while (ch < 0) { |
| if (this.readers.empty()) { |
| throw new IOException("Unexpected EOF"); |
| } |
| |
| this.currentReader.pbReader.close(); |
| this.currentReader = (StackedReader) this.readers.pop(); |
| ch = this.currentReader.pbReader.read(); |
| } |
| |
| return (char) ch; |
| } |
| |
| |
| /** |
| * Returns true if the current stream has no more characters left to be |
| * read. |
| * |
| * @throws java.io.IOException |
| * if an I/O error occurred |
| */ |
| public boolean atEOFOfCurrentStream() |
| throws IOException |
| { |
| int ch = this.currentReader.pbReader.read(); |
| |
| if (ch < 0) { |
| return true; |
| } else { |
| this.currentReader.pbReader.unread(ch); |
| return false; |
| } |
| } |
| |
| |
| /** |
| * Returns true if there are no more characters left to be read. |
| * |
| * @throws java.io.IOException |
| * if an I/O error occurred |
| */ |
| public boolean atEOF() |
| throws IOException |
| { |
| int ch = this.currentReader.pbReader.read(); |
| |
| while (ch < 0) { |
| if (this.readers.empty()) { |
| return true; |
| } |
| |
| this.currentReader.pbReader.close(); |
| this.currentReader = (StackedReader) this.readers.pop(); |
| ch = this.currentReader.pbReader.read(); |
| } |
| |
| this.currentReader.pbReader.unread(ch); |
| return false; |
| } |
| |
| |
| /** |
| * Pushes the last character read back to the stream. |
| * |
| * @param ch the character to push back. |
| * |
| * @throws java.io.IOException |
| * if an I/O error occurred |
| */ |
| public void unread(char ch) |
| throws IOException |
| { |
| this.currentReader.pbReader.unread(ch); |
| } |
| |
| |
| /** |
| * Opens a stream from a public and system ID. |
| * |
| * @param publicID the public ID, which may be null |
| * @param systemID the system ID, which is never null |
| * |
| * @throws java.net.MalformedURLException |
| * if the system ID does not contain a valid URL |
| * @throws java.io.FileNotFoundException |
| * if the system ID refers to a local file which does not exist |
| * @throws java.io.IOException |
| * if an error occurred opening the stream |
| */ |
| public Reader openStream(String publicID, |
| String systemID) |
| throws MalformedURLException, |
| FileNotFoundException, |
| IOException |
| { |
| URL url = new URL(this.currentReader.systemId, systemID); |
| |
| if (url.getRef() != null) { |
| String ref = url.getRef(); |
| |
| if (url.getFile().length() > 0) { |
| url = new URL(url.getProtocol(), url.getHost(), url.getPort(), |
| url.getFile()); |
| url = new URL("jar:" + url + '!' + ref); |
| } else { |
| url = StdXMLReader.class.getResource(ref); |
| } |
| } |
| |
| this.currentReader.publicId = publicID; |
| this.currentReader.systemId = url; |
| StringBuffer charsRead = new StringBuffer(); |
| Reader reader = this.stream2reader(url.openStream(), charsRead); |
| |
| if (charsRead.length() == 0) { |
| return reader; |
| } |
| |
| String charsReadStr = charsRead.toString(); |
| PushbackReader pbreader = new PushbackReader(reader, |
| charsReadStr.length()); |
| |
| for (int i = charsReadStr.length() - 1; i >= 0; i--) { |
| pbreader.unread(charsReadStr.charAt(i)); |
| } |
| |
| return pbreader; |
| } |
| |
| |
| /** |
| * Starts a new stream from a Java reader. The new stream is used |
| * temporary to read data from. If that stream is exhausted, control |
| * returns to the parent stream. |
| * |
| * @param reader the non-null reader to read the new data from |
| */ |
| public void startNewStream(Reader reader) |
| { |
| this.startNewStream(reader, false); |
| } |
| |
| |
| /** |
| * Starts a new stream from a Java reader. The new stream is used |
| * temporary to read data from. If that stream is exhausted, control |
| * returns to the parent stream. |
| * |
| * @param reader the non-null reader to read the new data from |
| * @param isInternalEntity true if the reader is produced by resolving |
| * an internal entity |
| */ |
| public void startNewStream(Reader reader, |
| boolean isInternalEntity) |
| { |
| StackedReader oldReader = this.currentReader; |
| this.readers.push(this.currentReader); |
| this.currentReader = new StackedReader(); |
| |
| if (isInternalEntity) { |
| this.currentReader.lineReader = null; |
| this.currentReader.pbReader = new PushbackReader(reader, 2); |
| } else { |
| this.currentReader.lineReader = new LineNumberReader(reader); |
| this.currentReader.pbReader |
| = new PushbackReader(this.currentReader.lineReader, 2); |
| } |
| |
| this.currentReader.systemId = oldReader.systemId; |
| this.currentReader.publicId = oldReader.publicId; |
| } |
| |
| |
| /** |
| * Returns the current "level" of the stream on the stack of streams. |
| */ |
| public int getStreamLevel() |
| { |
| return this.readers.size(); |
| } |
| |
| |
| /** |
| * Returns the line number of the data in the current stream. |
| */ |
| public int getLineNr() |
| { |
| if (this.currentReader.lineReader == null) { |
| StackedReader sr = (StackedReader) this.readers.peek(); |
| |
| if (sr.lineReader == null) { |
| return 0; |
| } else { |
| return sr.lineReader.getLineNumber() + 1; |
| } |
| } |
| |
| return this.currentReader.lineReader.getLineNumber() + 1; |
| } |
| |
| |
| /** |
| * Sets the system ID of the current stream. |
| * |
| * @param systemID the system ID |
| * |
| * @throws java.net.MalformedURLException |
| * if the system ID does not contain a valid URL |
| */ |
| public void setSystemID(String systemID) |
| throws MalformedURLException |
| { |
| this.currentReader.systemId = new URL(this.currentReader.systemId, |
| systemID); |
| } |
| |
| |
| /** |
| * Sets the public ID of the current stream. |
| * |
| * @param publicID the public ID |
| */ |
| public void setPublicID(String publicID) |
| { |
| this.currentReader.publicId = publicID; |
| } |
| |
| |
| /** |
| * Returns the current system ID. |
| */ |
| public String getSystemID() |
| { |
| return this.currentReader.systemId.toString(); |
| } |
| |
| |
| /** |
| * Returns the current public ID. |
| */ |
| public String getPublicID() |
| { |
| return this.currentReader.publicId; |
| } |
| |
| } |