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