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