/* * 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 org.freeplane.n3.nanoxml; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.PushbackInputStream; import java.io.PushbackReader; 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 * Modified by Dimitry Polivaev (2008) */ public class StdXMLReader implements IXMLReader { /** * A stacked reader. * * @author Marc De Scheemaecker * Modified by Dimitry Polivaev (2008) */ private class StackedReader { LineNumberReader lineReader; Reader pbReader; String publicId; URL systemId; } /** * 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 IXMLReader fileReader(final String filename) throws FileNotFoundException, IOException { final StdXMLReader r = new StdXMLReader(new FileInputStream(filename)); r.setSystemID(filename); for (int i = 0; i < r.readers.size(); i++) { final StackedReader sr = (StackedReader) r.readers.elementAt(i); sr.systemId = r.currentReader.systemId; } return r; } /** * Creates a new reader using a string as input. * * @param str * the string containing the XML data */ public static IXMLReader stringReader(final String str) { return new StdXMLReader(new StringReader(str)); } private char charReadTooMuch; /** * The current push-back reader. */ private StackedReader currentReader; /** * The stack of readers. */ final private Stack<StackedReader> readers; /** * Initializes the XML reader. * * @param stream * the input for the XML data. * @throws java.io.IOException * if an I/O error occurred */ public StdXMLReader(final InputStream stream) throws IOException { new PushbackInputStream(stream); final StringBuilder charsRead = new StringBuilder(); final Reader reader = this.stream2reader(stream, charsRead); currentReader = new StackedReader(); readers = new Stack<StackedReader>(); currentReader.lineReader = new LineNumberReader(reader); currentReader.pbReader = currentReader.lineReader; currentReader.publicId = ""; charReadTooMuch = '\0'; try { currentReader.systemId = new URL("file:."); } catch (final MalformedURLException e) { } this.startNewStream(new StringReader(charsRead.toString())); } /** * Initializes the XML reader. * * @param reader * the input for the XML data. */ public StdXMLReader(final Reader reader) { currentReader = new StackedReader(); readers = new Stack<StackedReader>(); currentReader.lineReader = new LineNumberReader(reader); currentReader.pbReader = currentReader.lineReader; currentReader.publicId = ""; charReadTooMuch = '\0'; try { currentReader.systemId = new URL("file:."); } catch (final MalformedURLException e) { } } /** * 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(final String publicID, String systemID) throws MalformedURLException, FileNotFoundException, IOException { URL systemIDasURL = null; charReadTooMuch = '\0'; try { systemIDasURL = new URL(systemID); } catch (final MalformedURLException e) { systemID = "file:" + systemID; try { systemIDasURL = new URL(systemID); } catch (final MalformedURLException e2) { throw e; } } currentReader = new StackedReader(); readers = new Stack<StackedReader>(); final Reader reader = this.openStream(publicID, systemIDasURL.toString()); currentReader.lineReader = new LineNumberReader(reader); currentReader.pbReader = currentReader.lineReader; } /** * 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 = readImpl(); while (ch < 0) { if (readers.empty()) { return true; } currentReader.pbReader.close(); currentReader = (StackedReader) readers.pop(); ch = readImpl(); } unread(ch); return false; } /** * 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 { final int ch = readImpl(); if (ch < 0) { return true; } else { unread(ch); return false; } } /** * Cleans up the object when it's destroyed. */ @Override protected void finalize() throws Throwable { currentReader.lineReader = null; currentReader.pbReader = null; currentReader.systemId = null; currentReader.publicId = null; currentReader = null; 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(final String str) { if (!str.startsWith("<?xml")) { return null; } int index = 5; while (index < str.length()) { final StringBuilder key = new StringBuilder(); 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; } final char delimiter = str.charAt(index); index++; final 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; } /** * Returns the line number of the data in the current stream. */ public int getLineNr() { if (currentReader.lineReader == null) { final StackedReader sr = (StackedReader) readers.peek(); if (sr.lineReader == null) { return 0; } else { return sr.lineReader.getLineNumber() + 1; } } return currentReader.lineReader.getLineNumber() + 1; } /** * Returns the current public ID. */ public String getPublicID() { return currentReader.publicId; } /** * Returns the current "level" of the stream on the stack of streams. */ public int getStreamLevel() { return readers.size(); } /** * Returns the current system ID. */ public String getSystemID() { return currentReader.systemId.toString(); } /** * 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(final String publicID, final String systemID) throws MalformedURLException, FileNotFoundException, IOException { URL url = new URL(currentReader.systemId, systemID); if (url.getRef() != null) { final 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); } } currentReader.publicId = publicID; currentReader.systemId = url; final StringBuilder charsRead = new StringBuilder(); final Reader reader = this.stream2reader(url.openStream(), charsRead); if (charsRead.length() == 0) { return reader; } final String charsReadStr = charsRead.toString(); final PushbackReader pbreader = new PushbackReader(reader, charsReadStr.length()); for (int i = charsReadStr.length() - 1; i >= 0; i--) { pbreader.unread(charsReadStr.charAt(i)); } return pbreader; } /** * Reads a character. * * @return the character * @throws java.io.IOException * if no character could be read */ public char read() throws IOException { int ch = readImpl(); while (ch < 0) { if (readers.empty()) { throw new IOException("Unexpected EOF"); } currentReader.pbReader.close(); currentReader = (StackedReader) readers.pop(); ch = readImpl(); } return (char) ch; } private int readImpl() throws IOException { if (charReadTooMuch != '\0') { final char ch = charReadTooMuch; charReadTooMuch = '\0'; return ch; } final int ch = currentReader.pbReader.read(); return ch; } /** * Sets the public ID of the current stream. * * @param publicID * the public ID */ public void setPublicID(final String publicID) { currentReader.publicId = publicID; } /** * 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(final String systemID) throws MalformedURLException { currentReader.systemId = new URL(currentReader.systemId, systemID); } /** * 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(final 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(final Reader reader, final boolean isInternalEntity) { final StackedReader oldReader = currentReader; readers.push(currentReader); currentReader = new StackedReader(); if (isInternalEntity) { currentReader.lineReader = null; currentReader.pbReader = reader; } else { currentReader.lineReader = new LineNumberReader(reader); currentReader.pbReader = new PushbackReader(currentReader.lineReader, 2); } currentReader.systemId = oldReader.systemId; currentReader.publicId = oldReader.publicId; } /** * 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(final InputStream stream, final StringBuilder charsRead) throws IOException { final 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); } final 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 (final UnsupportedEncodingException e) { return new InputStreamReader(pbstream, "UTF-8"); } default: charsRead.append((char) b); return new InputStreamReader(pbstream, "UTF-8"); } } public void unread(final char ch) throws IOException { charReadTooMuch = ch; } /** * 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(final int ch) throws IOException { unread((char) ch); } }