/* 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; } }