From 0f643ea4ea0268d803cbf66b5e36318357bc7825 Mon Sep 17 00:00:00 2001 From: benfry Date: Sun, 4 May 2008 23:31:43 +0000 Subject: [PATCH] upgrading to nanoxml instead of nanoxml-lite --- xml/src/processing/xml/CDATAReader.java | 193 + xml/src/processing/xml/ContentReader.java | 212 + xml/src/processing/xml/PIReader.java | 157 + xml/src/processing/xml/StdXMLBuilder.java | 358 ++ xml/src/processing/xml/StdXMLParser.java | 684 ++++ xml/src/processing/xml/StdXMLReader.java | 626 +++ xml/src/processing/xml/XMLAttribute.java | 153 + xml/src/processing/xml/XMLElement.java | 3600 +++++------------ xml/src/processing/xml/XMLEntityResolver.java | 173 + xml/src/processing/xml/XMLException.java | 286 ++ xml/src/processing/xml/XMLParseException.java | 164 +- xml/src/processing/xml/XMLUtil.java | 756 ++++ .../xml/XMLValidationException.java | 190 + xml/src/processing/xml/XMLValidator.java | 631 +++ xml/src/processing/xml/XMLWriter.java | 311 ++ 15 files changed, 5765 insertions(+), 2729 deletions(-) create mode 100644 xml/src/processing/xml/CDATAReader.java create mode 100644 xml/src/processing/xml/ContentReader.java create mode 100644 xml/src/processing/xml/PIReader.java create mode 100644 xml/src/processing/xml/StdXMLBuilder.java create mode 100644 xml/src/processing/xml/StdXMLParser.java create mode 100644 xml/src/processing/xml/StdXMLReader.java create mode 100644 xml/src/processing/xml/XMLAttribute.java create mode 100644 xml/src/processing/xml/XMLEntityResolver.java create mode 100644 xml/src/processing/xml/XMLException.java create mode 100644 xml/src/processing/xml/XMLUtil.java create mode 100644 xml/src/processing/xml/XMLValidationException.java create mode 100644 xml/src/processing/xml/XMLValidator.java create mode 100644 xml/src/processing/xml/XMLWriter.java diff --git a/xml/src/processing/xml/CDATAReader.java b/xml/src/processing/xml/CDATAReader.java new file mode 100644 index 000000000..11b6849a7 --- /dev/null +++ b/xml/src/processing/xml/CDATAReader.java @@ -0,0 +1,193 @@ +/* CDATAReader.java NanoXML/Java + * + * $Revision: 1.3 $ + * $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.Reader; +import java.io.IOException; + + +/** + * This reader reads data from another reader until the end of a CDATA section + * (]]>) has been encountered. + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.3 $ + */ +class CDATAReader + extends Reader +{ + + /** + * The encapsulated reader. + */ + private StdXMLReader reader; + + + /** + * Saved char. + */ + private char savedChar; + + + /** + * True if the end of the stream has been reached. + */ + private boolean atEndOfData; + + + /** + * Creates the reader. + * + * @param reader the encapsulated reader + */ + CDATAReader(StdXMLReader reader) + { + this.reader = reader; + this.savedChar = 0; + this.atEndOfData = false; + } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() + throws Throwable + { + this.reader = null; + super.finalize(); + } + + + /** + * Reads a block of data. + * + * @param buffer where to put the read data + * @param offset first position in buffer to put the data + * @param size maximum number of chars to read + * + * @return the number of chars read, or -1 if at EOF + * + * @throws java.io.IOException + * if an error occurred reading the data + */ + public int read(char[] buffer, + int offset, + int size) + throws IOException + { + int charsRead = 0; + + if (this.atEndOfData) { + return -1; + } + + if ((offset + size) > buffer.length) { + size = buffer.length - offset; + } + + while (charsRead < size) { + char ch = this.savedChar; + + if (ch == 0) { + ch = this.reader.read(); + } else { + this.savedChar = 0; + } + + if (ch == ']') { + char ch2 = this.reader.read(); + + if (ch2 == ']') { + char ch3 = this.reader.read(); + + if (ch3 == '>') { + this.atEndOfData = true; + break; + } + + this.savedChar = ch2; + this.reader.unread(ch3); + } else { + this.reader.unread(ch2); + } + } + buffer[charsRead] = ch; + charsRead++; + } + + if (charsRead == 0) { + charsRead = -1; + } + + return charsRead; + } + + + /** + * Skips remaining data and closes the stream. + * + * @throws java.io.IOException + * if an error occurred reading the data + */ + public void close() + throws IOException + { + while (! this.atEndOfData) { + char ch = this.savedChar; + + if (ch == 0) { + ch = this.reader.read(); + } else { + this.savedChar = 0; + } + + if (ch == ']') { + char ch2 = this.reader.read(); + + if (ch2 == ']') { + char ch3 = this.reader.read(); + + if (ch3 == '>') { + break; + } + + this.savedChar = ch2; + this.reader.unread(ch3); + } else { + this.reader.unread(ch2); + } + } + } + + this.atEndOfData = true; + } + +} diff --git a/xml/src/processing/xml/ContentReader.java b/xml/src/processing/xml/ContentReader.java new file mode 100644 index 000000000..66430c06b --- /dev/null +++ b/xml/src/processing/xml/ContentReader.java @@ -0,0 +1,212 @@ +/* ContentReader.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.Reader; +import java.io.IOException; + + +/** + * This reader reads data from another reader until a new element has + * been encountered. + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.4 $ + */ +class ContentReader + extends Reader +{ + + /** + * The encapsulated reader. + */ + private StdXMLReader reader; + + + /** + * Buffer. + */ + private String buffer; + + + /** + * Pointer into the buffer. + */ + private int bufferIndex; + + + /** + * The entity resolver. + */ + private XMLEntityResolver resolver; + + + /** + * Creates the reader. + * + * @param reader the encapsulated reader + * @param resolver the entity resolver + * @param buffer data that has already been read from reader + */ + ContentReader(StdXMLReader reader, + XMLEntityResolver resolver, + String buffer) + { + this.reader = reader; + this.resolver = resolver; + this.buffer = buffer; + this.bufferIndex = 0; + } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() + throws Throwable + { + this.reader = null; + this.resolver = null; + this.buffer = null; + super.finalize(); + } + + + /** + * Reads a block of data. + * + * @param outputBuffer where to put the read data + * @param offset first position in buffer to put the data + * @param size maximum number of chars to read + * + * @return the number of chars read, or -1 if at EOF + * + * @throws java.io.IOException + * if an error occurred reading the data + */ + public int read(char[] outputBuffer, + int offset, + int size) + throws IOException + { + try { + int charsRead = 0; + int bufferLength = this.buffer.length(); + + if ((offset + size) > outputBuffer.length) { + size = outputBuffer.length - offset; + } + + while (charsRead < size) { + String str = ""; + char ch; + + if (this.bufferIndex >= bufferLength) { + str = XMLUtil.read(this.reader, '&'); + ch = str.charAt(0); + } else { + ch = this.buffer.charAt(this.bufferIndex); + this.bufferIndex++; + outputBuffer[charsRead] = ch; + charsRead++; + continue; // don't interprete chars in the buffer + } + + if (ch == '<') { + this.reader.unread(ch); + break; + } + + if ((ch == '&') && (str.length() > 1)) { + if (str.charAt(1) == '#') { + ch = XMLUtil.processCharLiteral(str); + } else { + XMLUtil.processEntity(str, this.reader, this.resolver); + continue; + } + } + + outputBuffer[charsRead] = ch; + charsRead++; + } + + if (charsRead == 0) { + charsRead = -1; + } + + return charsRead; + } catch (XMLParseException e) { + throw new IOException(e.getMessage()); + } + } + + + /** + * Skips remaining data and closes the stream. + * + * @throws java.io.IOException + * if an error occurred reading the data + */ + public void close() + throws IOException + { + try { + int bufferLength = this.buffer.length(); + + for (;;) { + String str = ""; + char ch; + + if (this.bufferIndex >= bufferLength) { + str = XMLUtil.read(this.reader, '&'); + ch = str.charAt(0); + } else { + ch = this.buffer.charAt(this.bufferIndex); + this.bufferIndex++; + continue; // don't interprete chars in the buffer + } + + if (ch == '<') { + this.reader.unread(ch); + break; + } + + if ((ch == '&') && (str.length() > 1)) { + if (str.charAt(1) != '#') { + XMLUtil.processEntity(str, this.reader, this.resolver); + } + } + } + } catch (XMLParseException e) { + throw new IOException(e.getMessage()); + } + } + +} diff --git a/xml/src/processing/xml/PIReader.java b/xml/src/processing/xml/PIReader.java new file mode 100644 index 000000000..d6a2bf298 --- /dev/null +++ b/xml/src/processing/xml/PIReader.java @@ -0,0 +1,157 @@ +/* PIReader.java NanoXML/Java + * + * $Revision: 1.3 $ + * $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.Reader; +import java.io.IOException; + + +/** + * This reader reads data from another reader until the end of a processing + * instruction (?>) has been encountered. + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.3 $ + */ +class PIReader + extends Reader +{ + + /** + * The encapsulated reader. + */ + private StdXMLReader reader; + + + /** + * True if the end of the stream has been reached. + */ + private boolean atEndOfData; + + + /** + * Creates the reader. + * + * @param reader the encapsulated reader + */ + PIReader(StdXMLReader reader) + { + this.reader = reader; + this.atEndOfData = false; + } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() + throws Throwable + { + this.reader = null; + super.finalize(); + } + + + /** + * Reads a block of data. + * + * @param buffer where to put the read data + * @param offset first position in buffer to put the data + * @param size maximum number of chars to read + * + * @return the number of chars read, or -1 if at EOF + * + * @throws java.io.IOException + * if an error occurred reading the data + */ + public int read(char[] buffer, + int offset, + int size) + throws IOException + { + if (this.atEndOfData) { + return -1; + } + + int charsRead = 0; + + if ((offset + size) > buffer.length) { + size = buffer.length - offset; + } + + while (charsRead < size) { + char ch = this.reader.read(); + + if (ch == '?') { + char ch2 = this.reader.read(); + + if (ch2 == '>') { + this.atEndOfData = true; + break; + } + + this.reader.unread(ch2); + } + + buffer[charsRead] = ch; + charsRead++; + } + + if (charsRead == 0) { + charsRead = -1; + } + + return charsRead; + } + + + /** + * Skips remaining data and closes the stream. + * + * @throws java.io.IOException + * if an error occurred reading the data + */ + public void close() + throws IOException + { + while (! this.atEndOfData) { + char ch = this.reader.read(); + + if (ch == '?') { + char ch2 = this.reader.read(); + + if (ch2 == '>') { + this.atEndOfData = true; + } + } + } + } + +} diff --git a/xml/src/processing/xml/StdXMLBuilder.java b/xml/src/processing/xml/StdXMLBuilder.java new file mode 100644 index 000000000..2fb848994 --- /dev/null +++ b/xml/src/processing/xml/StdXMLBuilder.java @@ -0,0 +1,358 @@ +/* StdXMLBuilder.java NanoXML/Java + * + * $Revision: 1.3 $ + * $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.IOException; +import java.io.Reader; +import java.util.Stack; + + +/** + * StdXMLBuilder is a concrete implementation of IXMLBuilder which creates a + * tree of IXMLElement from an XML data source. + * + * @see net.n3.nanoxml.XMLElement + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.3 $ + */ +public class StdXMLBuilder +{ + + /** + * This stack contains the current element and its parents. + */ + private Stack stack; + + + /** + * The root element of the parsed XML tree. + */ + private XMLElement root; + + private XMLElement parent; + + /** + * Prototype element for creating the tree. + */ + //private XMLElement prototype; + + + /** + * Creates the builder. + */ + public StdXMLBuilder() + { + this.stack = null; + this.root = null; + //this(new XMLElement()); + } + + + public StdXMLBuilder(XMLElement parent) + { + this.parent = parent; + } + + + /** + * Creates the builder. + * + * @param prototype the prototype to use when building the tree. + */ +// public StdXMLBuilder(XMLElement prototype) +// { +// this.stack = null; +// this.root = null; +// this.prototype = prototype; +// } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() + throws Throwable + { + //this.prototype = null; + this.root = null; + this.stack.clear(); + this.stack = null; + super.finalize(); + } + + + /** + * This method is called before the parser starts processing its input. + * + * @param systemID the system ID of the XML data source. + * @param lineNr the line on which the parsing starts. + */ + public void startBuilding(String systemID, + int lineNr) + { + this.stack = new Stack(); + this.root = null; + } + + + /** + * This method is called when a processing instruction is encountered. + * PIs with target "xml" are handled by the parser. + * + * @param target the PI target. + * @param reader to read the data from the PI. + */ + public void newProcessingInstruction(String target, + Reader reader) + { + // nothing to do + } + + + /** + * This method is called when a new XML element is encountered. + * + * @see #endElement + * + * @param name the name of the element. + * @param nsPrefix the prefix used to identify the namespace. If no + * namespace has been specified, this parameter is null. + * @param nsURI the URI associated with the namespace. If no + * namespace has been specified, or no URI is + * associated with nsPrefix, this parameter is null. + * @param systemID the system ID of the XML data source. + * @param lineNr the line in the source where the element starts. + */ + public void startElement(String name, + String nsPrefix, + String nsURI, + String systemID, + int lineNr) + { + String fullName = name; + + if (nsPrefix != null) { + fullName = nsPrefix + ':' + name; + } + + //XMLElement elt = this.prototype.createElement(fullName, nsURI, + // systemID, lineNr); + +// XMLElement elt = new XMLElement(fullName, nsURI, systemID, lineNr); +// +// if (this.stack.empty()) { +// this.root = elt; +// } else { +// XMLElement top = (XMLElement) this.stack.peek(); +// top.addChild(elt); +// } +// stack.push(elt); + + if (this.stack.empty()) { + //System.out.println("setting root"); + parent.set(fullName, nsURI, systemID, lineNr); + stack.push(parent); + root = parent; + } else { + XMLElement top = (XMLElement) this.stack.peek(); + //System.out.println("stack has " + top.getName()); + XMLElement elt = new XMLElement(fullName, nsURI, systemID, lineNr); + top.addChild(elt); + stack.push(elt); + } + } + + + /** + * This method is called when the attributes of an XML element have been + * processed. + * + * @see #startElement + * @see #addAttribute + * + * @param name the name of the element. + * @param nsPrefix the prefix used to identify the namespace. If no + * namespace has been specified, this parameter is null. + * @param nsURI the URI associated with the namespace. If no + * namespace has been specified, or no URI is + * associated with nsPrefix, this parameter is null. + */ + public void elementAttributesProcessed(String name, + String nsPrefix, + String nsURI) + { + // nothing to do + } + + + /** + * This method is called when the end of an XML elemnt is encountered. + * + * @see #startElement + * + * @param name the name of the element. + * @param nsPrefix the prefix used to identify the namespace. If no + * namespace has been specified, this parameter is null. + * @param nsURI the URI associated with the namespace. If no + * namespace has been specified, or no URI is + * associated with nsPrefix, this parameter is null. + */ + public void endElement(String name, + String nsPrefix, + String nsURI) + { + XMLElement elt = (XMLElement) this.stack.pop(); + + if (elt.getChildCount() == 1) { + XMLElement child = elt.getChildAtIndex(0); + + if (child.getName() == null) { + elt.setContent(child.getContent()); + elt.removeChildAtIndex(0); + } + } + } + + + /** + * This method is called when a new attribute of an XML element is + * encountered. + * + * @param key the key (name) of the attribute. + * @param nsPrefix the prefix used to identify the namespace. If no + * namespace has been specified, this parameter is null. + * @param nsURI the URI associated with the namespace. If no + * namespace has been specified, or no URI is + * associated with nsPrefix, this parameter is null. + * @param value the value of the attribute. + * @param type the type of the attribute. If no type is known, + * "CDATA" is returned. + * + * @throws java.lang.Exception + * If an exception occurred while processing the event. + */ + public void addAttribute(String key, + String nsPrefix, + String nsURI, + String value, + String type) + throws Exception + { + String fullName = key; + + if (nsPrefix != null) { + fullName = nsPrefix + ':' + key; + } + + XMLElement top = (XMLElement) this.stack.peek(); + + if (top.hasAttribute(fullName)) { + throw new XMLParseException(top.getSystemID(), + top.getLineNr(), + "Duplicate attribute: " + key); + } + + if (nsPrefix != null) { + top.setAttribute(fullName, nsURI, value); + } else { + top.setAttribute(fullName, value); + } + } + + + /** + * This method is called when a PCDATA element is encountered. A Java + * reader is supplied from which you can read the data. The reader will + * only read the data of the element. You don't need to check for + * boundaries. If you don't read the full element, the rest of the data + * is skipped. You also don't have to care about entities; they are + * resolved by the parser. + * + * @param reader the Java reader from which you can retrieve the data. + * @param systemID the system ID of the XML data source. + * @param lineNr the line in the source where the element starts. + */ + public void addPCData(Reader reader, + String systemID, + int lineNr) + { + int bufSize = 2048; + int sizeRead = 0; + StringBuffer str = new StringBuffer(bufSize); + char[] buf = new char[bufSize]; + + for (;;) { + if (sizeRead >= bufSize) { + bufSize *= 2; + str.ensureCapacity(bufSize); + } + + int size; + + try { + size = reader.read(buf); + } catch (IOException e) { + break; + } + + if (size < 0) { + break; + } + + str.append(buf, 0, size); + sizeRead += size; + } + + //XMLElement elt = this.prototype.createElement(null, systemID, lineNr); + XMLElement elt = new XMLElement(null, null, systemID, lineNr); + elt.setContent(str.toString()); + + if (! this.stack.empty()) { + XMLElement top = (XMLElement) this.stack.peek(); + top.addChild(elt); + } + } + + + /** + * Returns the result of the building process. This method is called just + * before the parse method of IXMLParser returns. + * + * @see net.n3.nanoxml.IXMLParser#parse + * + * @return the result of the building process. + */ + public Object getResult() + { + return this.root; + } + +} diff --git a/xml/src/processing/xml/StdXMLParser.java b/xml/src/processing/xml/StdXMLParser.java new file mode 100644 index 000000000..c286a0b9d --- /dev/null +++ b/xml/src/processing/xml/StdXMLParser.java @@ -0,0 +1,684 @@ +/* StdXMLParser.java NanoXML/Java + * + * $Revision: 1.5 $ + * $Date: 2002/03/24 11:37:00 $ + * $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.Reader; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; + + +/** + * StdXMLParser is the core parser of NanoXML. + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.5 $ + */ +public class StdXMLParser { + + /** + * The builder which creates the logical structure of the XML data. + */ + private StdXMLBuilder builder; + + + /** + * The reader from which the parser retrieves its data. + */ + private StdXMLReader reader; + + + /** + * The entity resolver. + */ + private XMLEntityResolver entityResolver; + + + /** + * The validator that will process entity references and validate the XML + * data. + */ + private XMLValidator validator; + + + /** + * Creates a new parser. + */ + public StdXMLParser() + { + this.builder = null; + this.validator = null; + this.reader = null; + this.entityResolver = new XMLEntityResolver(); + } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() + throws Throwable + { + this.builder = null; + this.reader = null; + this.entityResolver = null; + this.validator = null; + super.finalize(); + } + + + /** + * Sets the builder which creates the logical structure of the XML data. + * + * @param builder the non-null builder + */ + public void setBuilder(StdXMLBuilder builder) + { + this.builder = builder; + } + + + /** + * Returns the builder which creates the logical structure of the XML data. + * + * @return the builder + */ + public StdXMLBuilder getBuilder() + { + return this.builder; + } + + + /** + * Sets the validator that validates the XML data. + * + * @param validator the non-null validator + */ + public void setValidator(XMLValidator validator) + { + this.validator = validator; + } + + + /** + * Returns the validator that validates the XML data. + * + * @return the validator + */ + public XMLValidator getValidator() + { + return this.validator; + } + + + /** + * Sets the entity resolver. + * + * @param resolver the non-null resolver + */ + public void setResolver(XMLEntityResolver resolver) + { + this.entityResolver = resolver; + } + + + /** + * Returns the entity resolver. + * + * @return the non-null resolver + */ + public XMLEntityResolver getResolver() + { + return this.entityResolver; + } + + + /** + * Sets the reader from which the parser retrieves its data. + * + * @param reader the reader + */ + public void setReader(StdXMLReader reader) + { + this.reader = reader; + } + + + /** + * Returns the reader from which the parser retrieves its data. + * + * @return the reader + */ + public StdXMLReader getReader() + { + return this.reader; + } + + + /** + * Parses the data and lets the builder create the logical data structure. + * + * @return the logical structure built by the builder + * + * @throws net.n3.nanoxml.XMLException + * if an error occurred reading or parsing the data + */ + public Object parse() + throws XMLException + { + try { + this.builder.startBuilding(this.reader.getSystemID(), + this.reader.getLineNr()); + this.scanData(); + return this.builder.getResult(); + } catch (XMLException e) { + throw e; + } catch (Exception e) { + throw new XMLException(e); + } + } + + + /** + * Scans the XML data for elements. + * + * @throws java.lang.Exception + * if something went wrong + */ + protected void scanData() throws Exception { + while ((! this.reader.atEOF()) && (this.builder.getResult() == null)) { + String str = XMLUtil.read(this.reader, '&'); + char ch = str.charAt(0); + if (ch == '&') { + XMLUtil.processEntity(str, this.reader, this.entityResolver); + continue; + } + + switch (ch) { + case '<': + this.scanSomeTag(false, // don't allow CDATA + null, // no default namespace + new Properties()); + break; + + case ' ': + case '\t': + case '\r': + case '\n': + // skip whitespace + break; + + default: + XMLUtil.errorInvalidInput(reader.getSystemID(), + reader.getLineNr(), + "`" + ch + "' (0x" + + Integer.toHexString((int) ch) + + ')'); + } + } + } + + + /** + * Scans an XML tag. + * + * @param allowCDATA true if CDATA sections are allowed at this point + * @param defaultNamespace the default namespace URI (or null) + * @param namespaces list of defined namespaces + * + * @throws java.lang.Exception + * if something went wrong + */ + protected void scanSomeTag(boolean allowCDATA, + String defaultNamespace, + Properties namespaces) + throws Exception + { + String str = XMLUtil.read(this.reader, '&'); + char ch = str.charAt(0); + + if (ch == '&') { + XMLUtil.errorUnexpectedEntity(reader.getSystemID(), + reader.getLineNr(), + str); + } + + switch (ch) { + case '?': + this.processPI(); + break; + + case '!': + this.processSpecialTag(allowCDATA); + break; + + default: + this.reader.unread(ch); + this.processElement(defaultNamespace, namespaces); + } + } + + + /** + * Processes a "processing instruction". + * + * @throws java.lang.Exception + * if something went wrong + */ + protected void processPI() + throws Exception + { + XMLUtil.skipWhitespace(this.reader, null); + String target = XMLUtil.scanIdentifier(this.reader); + XMLUtil.skipWhitespace(this.reader, null); + Reader reader = new PIReader(this.reader); + + if (! target.equalsIgnoreCase("xml")) { + this.builder.newProcessingInstruction(target, reader); + } + + reader.close(); + } + + + /** + * Processes a tag that starts with a bang (<!...>). + * + * @param allowCDATA true if CDATA sections are allowed at this point + * + * @throws java.lang.Exception + * if something went wrong + */ + protected void processSpecialTag(boolean allowCDATA) + throws Exception + { + String str = XMLUtil.read(this.reader, '&'); + char ch = str.charAt(0); + + if (ch == '&') { + XMLUtil.errorUnexpectedEntity(reader.getSystemID(), + reader.getLineNr(), + str); + } + + switch (ch) { + case '[': + if (allowCDATA) { + this.processCDATA(); + } else { + XMLUtil.errorUnexpectedCDATA(reader.getSystemID(), + reader.getLineNr()); + } + + return; + + case 'D': + this.processDocType(); + return; + + case '-': + XMLUtil.skipComment(this.reader); + return; + } + } + + + /** + * Processes a CDATA section. + * + * @throws java.lang.Exception + * if something went wrong + */ + protected void processCDATA() + throws Exception + { + if (! XMLUtil.checkLiteral(this.reader, "CDATA[")) { + XMLUtil.errorExpectedInput(reader.getSystemID(), + reader.getLineNr(), + "') { + XMLUtil.errorExpectedInput(reader.getSystemID(), + reader.getLineNr(), + "`>'"); + } + + if (false) { // TODO currently disabled + if (systemID != null) { + Reader reader = this.reader.openStream(publicID.toString(), + systemID); + this.reader.startNewStream(reader); + this.reader.setSystemID(systemID); + this.reader.setPublicID(publicID.toString()); + this.validator.parseDTD(publicID.toString(), + this.reader, + this.entityResolver, + true); + } + } + } + + + /** + * Processes a regular element. + * + * @param defaultNamespace the default namespace URI (or null) + * @param namespaces list of defined namespaces + * + * @throws java.lang.Exception + * if something went wrong + */ + protected void processElement(String defaultNamespace, + Properties namespaces) + throws Exception + { + String fullName = XMLUtil.scanIdentifier(this.reader); + String name = fullName; + XMLUtil.skipWhitespace(this.reader, null); + String prefix = null; + int colonIndex = name.indexOf(':'); + + if (colonIndex > 0) { + prefix = name.substring(0, colonIndex); + name = name.substring(colonIndex + 1); + } + + Vector attrNames = new Vector(); + Vector attrValues = new Vector(); + Vector attrTypes = new Vector(); + + this.validator.elementStarted(fullName, + this.reader.getSystemID(), + this.reader.getLineNr()); + char ch; + + for (;;) { + ch = this.reader.read(); + + if ((ch == '/') || (ch == '>')) { + break; + } + + this.reader.unread(ch); + this.processAttribute(attrNames, attrValues, attrTypes); + XMLUtil.skipWhitespace(this.reader, null); + } + + Properties extraAttributes = new Properties(); + this.validator.elementAttributesProcessed(fullName, + extraAttributes, + this.reader.getSystemID(), + this.reader.getLineNr()); + Enumeration en = extraAttributes.keys(); + + while (en.hasMoreElements()) { + String key = (String) en.nextElement(); + String value = extraAttributes.getProperty(key); + attrNames.addElement(key); + attrValues.addElement(value); + attrTypes.addElement("CDATA"); + } + + for (int i = 0; i < attrNames.size(); i++) { + String key = (String) attrNames.elementAt(i); + String value = (String) attrValues.elementAt(i); + //String type = (String) attrTypes.elementAt(i); + + if (key.equals("xmlns")) { + defaultNamespace = value; + } else if (key.startsWith("xmlns:")) { + namespaces.put(key.substring(6), value); + } + } + + if (prefix == null) { + this.builder.startElement(name, prefix, defaultNamespace, + this.reader.getSystemID(), + this.reader.getLineNr()); + } else { + this.builder.startElement(name, prefix, + namespaces.getProperty(prefix), + this.reader.getSystemID(), + this.reader.getLineNr()); + } + + for (int i = 0; i < attrNames.size(); i++) { + String key = (String) attrNames.elementAt(i); + + if (key.startsWith("xmlns")) { + continue; + } + + String value = (String) attrValues.elementAt(i); + String type = (String) attrTypes.elementAt(i); + colonIndex = key.indexOf(':'); + + if (colonIndex > 0) { + String attPrefix = key.substring(0, colonIndex); + key = key.substring(colonIndex + 1); + this.builder.addAttribute(key, attPrefix, + namespaces.getProperty(attPrefix), + value, type); + } else { + this.builder.addAttribute(key, null, null, value, type); + } + } + + if (prefix == null) { + this.builder.elementAttributesProcessed(name, prefix, + defaultNamespace); + } else { + this.builder.elementAttributesProcessed(name, prefix, + namespaces + .getProperty(prefix)); + } + + if (ch == '/') { + if (this.reader.read() != '>') { + XMLUtil.errorExpectedInput(reader.getSystemID(), + reader.getLineNr(), + "`>'"); + } + + this.validator.elementEnded(name, + this.reader.getSystemID(), + this.reader.getLineNr()); + + if (prefix == null) { + this.builder.endElement(name, prefix, defaultNamespace); + } else { + this.builder.endElement(name, prefix, + namespaces.getProperty(prefix)); + } + + return; + } + + StringBuffer buffer = new StringBuffer(16); + + for (;;) { + buffer.setLength(0); + String str; + + for (;;) { + XMLUtil.skipWhitespace(this.reader, buffer); + str = XMLUtil.read(this.reader, '&'); + + if ((str.charAt(0) == '&') && (str.charAt(1) != '#')) { + XMLUtil.processEntity(str, this.reader, + this.entityResolver); + } else { + break; + } + } + + if (str.charAt(0) == '<') { + str = XMLUtil.read(this.reader, '\0'); + + if (str.charAt(0) == '/') { + XMLUtil.skipWhitespace(this.reader, null); + str = XMLUtil.scanIdentifier(this.reader); + + if (! str.equals(fullName)) { + XMLUtil.errorWrongClosingTag(reader.getSystemID(), + reader.getLineNr(), + name, str); + } + + XMLUtil.skipWhitespace(this.reader, null); + + if (this.reader.read() != '>') { + XMLUtil.errorClosingTagNotEmpty(reader.getSystemID(), + reader.getLineNr()); + } + + this.validator.elementEnded(fullName, + this.reader.getSystemID(), + this.reader.getLineNr()); + if (prefix == null) { + this.builder.endElement(name, prefix, defaultNamespace); + } else { + this.builder.endElement(name, prefix, + namespaces.getProperty(prefix)); + } + break; + } else { // <[^/] + this.reader.unread(str.charAt(0)); + this.scanSomeTag(true, //CDATA allowed + defaultNamespace, + (Properties) namespaces.clone()); + } + } else { // [^<] + if (str.charAt(0) == '&') { + ch = XMLUtil.processCharLiteral(str); + buffer.append(ch); + } else { + reader.unread(str.charAt(0)); + } + this.validator.PCDataAdded(this.reader.getSystemID(), + this.reader.getLineNr()); + Reader r = new ContentReader(this.reader, + this.entityResolver, + buffer.toString()); + this.builder.addPCData(r, this.reader.getSystemID(), + this.reader.getLineNr()); + r.close(); + } + } + } + + + /** + * Processes an attribute of an element. + * + * @param attrNames contains the names of the attributes. + * @param attrValues contains the values of the attributes. + * @param attrTypes contains the types of the attributes. + * + * @throws java.lang.Exception + * if something went wrong + */ + protected void processAttribute(Vector attrNames, + Vector attrValues, + Vector attrTypes) + throws Exception + { + String key = XMLUtil.scanIdentifier(this.reader); + XMLUtil.skipWhitespace(this.reader, null); + + if (! XMLUtil.read(this.reader, '&').equals("=")) { + XMLUtil.errorExpectedInput(reader.getSystemID(), + reader.getLineNr(), + "`='"); + } + + XMLUtil.skipWhitespace(this.reader, null); + String value = XMLUtil.scanString(this.reader, '&', + this.entityResolver); + attrNames.addElement(key); + attrValues.addElement(value); + attrTypes.addElement("CDATA"); + this.validator.attributeAdded(key, value, + this.reader.getSystemID(), + this.reader.getLineNr()); + } + +} diff --git a/xml/src/processing/xml/StdXMLReader.java b/xml/src/processing/xml/StdXMLReader.java new file mode 100644 index 000000000..9914cee49 --- /dev/null +++ b/xml/src/processing/xml/StdXMLReader.java @@ -0,0 +1,626 @@ +/* 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 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(); + 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(); + 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("= '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(); + 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; + } + +} diff --git a/xml/src/processing/xml/XMLAttribute.java b/xml/src/processing/xml/XMLAttribute.java new file mode 100644 index 000000000..759dd47fc --- /dev/null +++ b/xml/src/processing/xml/XMLAttribute.java @@ -0,0 +1,153 @@ +/* XMLAttribute.java NanoXML/Java + * + * $Revision: 1.4 $ + * $Date: 2002/01/04 21:03:29 $ + * $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; + + +/** + * An attribute in an XML element. This is an internal class. + * + * @see net.n3.nanoxml.XMLElement + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.4 $ + */ +class XMLAttribute +{ + + /** + * The full name of the attribute. + */ + private String fullName; + + + /** + * The short name of the attribute. + */ + private String name; + + + /** + * The namespace URI of the attribute. + */ + private String namespace; + + + /** + * The value of the attribute. + */ + private String value; + + + /** + * The type of the attribute. + */ + private String type; + + + /** + * Creates a new attribute. + * + * @param fullName the non-null full name + * @param name the non-null short name + * @param namespace the namespace URI, which may be null + * @param value the value of the attribute + * @param type the type of the attribute + */ + XMLAttribute(String fullName, + String name, + String namespace, + String value, + String type) + { + this.fullName = fullName; + this.name = name; + this.namespace = namespace; + this.value = value; + this.type = type; + } + + + /** + * Returns the full name of the attribute. + */ + String getFullName() + { + return this.fullName; + } + + + /** + * Returns the short name of the attribute. + */ + String getName() + { + return this.name; + } + + + /** + * Returns the namespace of the attribute. + */ + String getNamespace() + { + return this.namespace; + } + + + /** + * Returns the value of the attribute. + */ + String getValue() + { + return this.value; + } + + + /** + * Sets the value of the attribute. + * + * @param value the new value. + */ + void setValue(String value) + { + this.value = value; + } + + + /** + * Returns the type of the attribute. + * + * @param type the new type. + */ + String getType() + { + return this.type; + } + +} diff --git a/xml/src/processing/xml/XMLElement.java b/xml/src/processing/xml/XMLElement.java index 568ec7c58..64245521f 100644 --- a/xml/src/processing/xml/XMLElement.java +++ b/xml/src/processing/xml/XMLElement.java @@ -1,471 +1,220 @@ -/* - This code is from NanoXML 2.2.3 Lite - Copyright (C) 2000-2002 Marc De Scheemaecker, All Rights Reserved. - - Additional modifications Copyright (c) 2006 Ben Fry and Casey Reas - to make the code better-suited for use with Processing. - - Original license notice from Marc De Scheemaecker: - - 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. -*/ +/* XMLElement.java NanoXML/Java + * + * $Revision: 1.5 $ + * $Date: 2002/02/06 18:50:12 $ + * $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.IOException; import java.io.ByteArrayOutputStream; -import java.io.CharArrayReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; +import java.io.Serializable; import java.io.StringReader; -import java.io.Writer; import java.util.Enumeration; -import java.util.Hashtable; +import java.util.Properties; import java.util.Vector; import processing.core.PApplet; /** - * XMLElement is a representation of an XML object. The object is able to parse - * XML code. - *

- * The code is from NanoXML 2.2.3 Lite from Marc De Scheemaecker. His project is - * online at http://nanoxml.cyberelf.be/. - * Some interfaces to the code have been changed, and the package naming has been - * altered, however it should be clear that this code is almost entirely his - * work, with no connection to the Processing project. - *

- * Alterations/additions for the Processing library:

- * The intent of this library (with regard to Processing) is to provide an - * extremely simple (and compact) means of reading and writing XML data from a - * sketch. As such, this is not a full-featured library for handling XML data. - * For those who need it, more sophisticated libraries are available, and there - * are no plans to add significant new features to this library. - *

- * The encoding parameter inside XML files is ignored, all files are - * parsed with UTF-8 encoding (as of release 0134). - * - * import processing.xml.*; + * XMLElement is an XML element. The standard NanoXML builder generates a + * tree of such elements. + * + * @see net.n3.nanoxml.StdXMLBuilder * - * XMLElement xml = new XMLElement(this, "filename.xml"); - * int childCount = xml.getChildCount(); - * for (int i = 0; i < childCount; i++) { - * XMLElement kid = xml.getChild(i); - * float attr = kid.getFloatAttribute("some-attribute"); - * println("some-attribute is " + attr); - * } - * * @author Marc De Scheemaecker - * @author Ben Fry + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.5 $ */ -public class XMLElement -{ - static final boolean DEBUG = false; - +public class XMLElement implements Serializable { /** - * The attributes given to the element. - * - *

Invariants:
- *
  • The field can be empty. - *
  • The field is never null. - *
  • The keys and the values are strings. - *
+ * Necessary for serialization. */ - private Hashtable attributes; + static final long serialVersionUID = -2383376380548624920L; /** - * Child elements of the element. - * - *
Invariants:
- *
  • The field can be empty. - *
  • The field is never null. - *
  • The elements are instances of XMLElement - * or a subclass of XMLElement. - *
+ * No line number defined. + */ + public static final int NO_LINE = -1; + + + /** + * The parent element. + */ + private XMLElement parent; + + + /** + * The attributes of the element. + */ + private Vector attributes; + + + /** + * The child elements. */ private Vector children; /** * The name of the element. - * - *
Invariants:
- *
  • The field is null iff the element is not - * initialized by either parse or setName. - *
  • If the field is not null, it's not empty. - *
  • If the field is not null, it contains a valid - * XML identifier. - *
*/ private String name; /** - * The #PCDATA content of the object. - * - *
Invariants:
- *
  • The field is null iff the element is not a - * #PCDATA element. - *
  • The field can be any string, including the empty string. - *
+ * The full name of the element. + */ + private String fullName; + + + /** + * The namespace URI. + */ + private String namespace; + + + /** + * The content of the element. */ private String content; /** - * Conversion table for &...; entities. The keys are the entity names - * without the & and ; delimiters. - * - *
Invariants:
- *
  • The field is never null. - *
  • The field always contains the following associations: - * "lt" => "<", "gt" => ">", - * "quot" => "\"", "apos" => "'", - * "amp" => "&" - *
  • The keys are strings - *
  • The values are char arrays - *
+ * The system ID of the source data where this element is located. */ - private Hashtable entities; + private String systemID; /** - * Use this to leave unknown entities unchanged. This is a necessity because - * lines are not currently parsed, and cause dumb problems - * with Illustrator SVG files. [fry] + * The line in the source data where this element starts. */ - private boolean ignoreUnknownEntities = true; + private int lineNr; /** - * For attributes such as NOWRAP that aren't set equal to anything, - * parse without giving an error, and set their contents to an empty String. + * Creates an empty element to be used for #PCDATA content. */ - private boolean ignoreMissingAttributes = true; - - - /** - * The line number where the element starts. - * - *
Invariants:
- *
  • lineNumber >= 0 - *
- */ - private int lineNumber; - - - /** - * true if the case of the element and attribute names - * are case insensitive. - */ - private boolean ignoreCase; - - - /** - * true if the leading and trailing whitespace of #PCDATA - * sections have to be ignored. - */ - private boolean ignoreWhitespace; - - - /** - * Character read too much. - * This character provides push-back functionality to the input reader - * without having to use a PushbackReader. - * If there is no such character, this field is '\0'. - */ - private char charReadTooMuch; - - - /** - * The reader provided by the caller of the parse method. - * - *
Invariants:
- *
  • The field is not null while the parse method - * is running. - *
- */ - private Reader reader; - - - /** - * The current line number in the source content. - * - *
Invariants:
- *
  • parserLineNumber > 0 while the parse method is running. - *
- */ - private int parserLineNumber; - - - /** - * Creates and initializes a new XML element. - * Calling the construction is equivalent to: - * - * - *
Postconditions:
- *
  • countChildren() => 0 - *
  • enumerateChildren() => empty enumeration - *
  • enumeratePropertyNames() => empty enumeration - *
  • getChildren() => empty vector - *
  • getContent() => "" - *
  • getLineNumber() => 0 - *
  • getName() => null - *
- * - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable) - * XMLElement(Hashtable) - * @see processing.xml.XMLElement#XMLElement(boolean) - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable,boolean) - * XMLElement(Hashtable, boolean) - */ - public XMLElement() - { - this(new Hashtable(), false, true, true); + public XMLElement() { + this(null, null, null, NO_LINE); + } + + + protected void set(String fullName, + String namespace, + String systemID, + int lineNr) { + this.fullName = fullName; + if (namespace == null) { + this.name = fullName; + } else { + int index = fullName.indexOf(':'); + if (index >= 0) { + this.name = fullName.substring(index + 1); + } else { + this.name = fullName; + } + } + this.namespace = namespace; + this.lineNr = lineNr; + this.systemID = systemID; } /** - * Creates and initializes a new XML element. - * Calling the construction is equivalent to: - * + * Creates an empty element. * - * @param entities - * The entity conversion table. - * - *
Preconditions:
- *
  • entities != null - *
- * - *
Postconditions:
- *
  • countChildren() => 0 - *
  • enumerateChildren() => empty enumeration - *
  • enumeratePropertyNames() => empty enumeration - *
  • getChildren() => empty vector - *
  • getContent() => "" - *
  • getLineNumber() => 0 - *
  • getName() => null - *
- * - * @see processing.xml.XMLElement#XMLElement() - * @see processing.xml.XMLElement#XMLElement(boolean) - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable,boolean) - * XMLElement(Hashtable, boolean) + * @param fullName the name of the element. */ - public XMLElement(Hashtable entities) - { - this(entities, false, true, true); - } +// public XMLElement(String fullName) { +// this(fullName, null, null, NO_LINE); +// } /** - * Creates and initializes a new XML element. - * Calling the construction is equivalent to: - * + * Creates an empty element. * - * @param skipLeadingWhitespace - * true if leading and trailing whitespace in PCDATA - * content has to be removed. - * - *
Postconditions:
- *
  • countChildren() => 0 - *
  • enumerateChildren() => empty enumeration - *
  • enumeratePropertyNames() => empty enumeration - *
  • getChildren() => empty vector - *
  • getContent() => "" - *
  • getLineNumber() => 0 - *
  • getName() => null - *
- * - * @see processing.xml.XMLElement#XMLElement() - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable) - * XMLElement(Hashtable) - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable,boolean) - * XMLElement(Hashtable, boolean) + * @param fullName the name of the element. + * @param systemID the system ID of the XML data where the element starts. + * @param lineNr the line in the XML data where the element starts. */ - public XMLElement(boolean skipLeadingWhitespace) - { - this(new Hashtable(), skipLeadingWhitespace, true, true); - } +// public XMLElement(String fullName, +// String systemID, +// int lineNr) { +// this(fullName, null, systemID, lineNr); +// } /** - * Creates and initializes a new XML element. - * Calling the construction is equivalent to: - * + * Creates an empty element. * - * @param entities - * The entity conversion table. - * @param skipLeadingWhitespace - * true if leading and trailing whitespace in PCDATA - * content has to be removed. - * - *
Preconditions:
- *
  • entities != null - *
- * - *
Postconditions:
- *
  • countChildren() => 0 - *
  • enumerateChildren() => empty enumeration - *
  • enumeratePropertyNames() => empty enumeration - *
  • getChildren() => empty vector - *
  • getContent() => "" - *
  • getLineNumber() => 0 - *
  • getName() => null - *
- * - * @see processing.xml.XMLElement#XMLElement() - * @see processing.xml.XMLElement#XMLElement(boolean) - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable) - * XMLElement(Hashtable) + * @param fullName the full name of the element + * @param namespace the namespace URI. */ - public XMLElement(Hashtable entities, - boolean skipLeadingWhitespace) - { - this(entities, skipLeadingWhitespace, true, true); - } +// public XMLElement(String fullName, +// String namespace) { +// this(fullName, namespace, null, NO_LINE); +// } /** - * Creates and initializes a new XML element. + * Creates an empty element. * - * @param entities - * The entity conversion table. - * @param skipLeadingWhitespace - * true if leading and trailing whitespace in PCDATA - * content has to be removed. - * @param ignoreCase - * true if the case of element and attribute names have - * to be ignored. - * - *
Preconditions:
- *
  • entities != null - *
- * - *
Postconditions:
- *
  • countChildren() => 0 - *
  • enumerateChildren() => empty enumeration - *
  • enumeratePropertyNames() => empty enumeration - *
  • getChildren() => empty vector - *
  • getContent() => "" - *
  • getLineNumber() => 0 - *
  • getName() => null - *
- * - * @see processing.xml.XMLElement#XMLElement() - * @see processing.xml.XMLElement#XMLElement(boolean) - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable) - * XMLElement(Hashtable) - * @see processing.xml.XMLElement#XMLElement(java.util.Hashtable,boolean) - * XMLElement(Hashtable, boolean) + * @param fullName the full name of the element + * @param namespace the namespace URI. + * @param systemID the system ID of the XML data where the element starts. + * @param lineNr the line in the XML data where the element starts. */ - public XMLElement(Hashtable entities, - boolean skipLeadingWhitespace, - boolean ignoreCase) - { - this(entities, skipLeadingWhitespace, true, ignoreCase); - } - - - /** - * Creates and initializes a new XML element. - *

- * This constructor should only be called from - * {@link #createAnotherElement() createAnotherElement} - * to create child elements. - * - * @param entities - * The entity conversion table. - * @param skipLeadingWhitespace - * true if leading and trailing whitespace in PCDATA - * content has to be removed. - * @param fillBasicConversionTable - * true if the basic entities need to be added to - * the entity list. - * @param ignoreCase - * true if the case of element and attribute names have - * to be ignored. - * - *

Preconditions:
- *
  • entities != null - *
  • if fillBasicConversionTable == false - * then entities contains at least the following - * entries: amp, lt, gt, - * apos and quot - *
- * - *
Postconditions:
- *
  • countChildren() => 0 - *
  • enumerateChildren() => empty enumeration - *
  • enumeratePropertyNames() => empty enumeration - *
  • getChildren() => empty vector - *
  • getContent() => "" - *
  • getLineNumber() => 0 - *
  • getName() => null - *
- * - * @see processing.xml.XMLElement#createAnotherElement() - */ - protected XMLElement(Hashtable entities, - boolean skipLeadingWhitespace, - boolean fillBasicConversionTable, - boolean ignoreCase) - { - this.ignoreWhitespace = skipLeadingWhitespace; - this.ignoreCase = ignoreCase; - this.name = null; - this.content = ""; - this.attributes = new Hashtable(); - this.children = new Vector(); - this.entities = entities; - this.lineNumber = 0; - Enumeration en = this.entities.keys(); - while (en.hasMoreElements()) { - Object key = en.nextElement(); - Object value = this.entities.get(key); - if (value instanceof String) { - value = ((String) value).toCharArray(); - this.entities.put(key, value); + public XMLElement(String fullName, + String namespace, + String systemID, + int lineNr) { + this.attributes = new Vector(); + this.children = new Vector(8); + this.fullName = fullName; + if (namespace == null) { + this.name = fullName; + } else { + int index = fullName.indexOf(':'); + if (index >= 0) { + this.name = fullName.substring(index + 1); + } else { + this.name = fullName; } } - if (fillBasicConversionTable) { - this.entities.put("amp", new char[] { '&' }); - this.entities.put("quot", new char[] { '"' }); - this.entities.put("apos", new char[] { '\'' }); - this.entities.put("lt", new char[] { '<' }); - this.entities.put("gt", new char[] { '>' }); - //this.entities.put("nbsp", new char[] { 0xA0 }); - } + this.namespace = namespace; + this.content = null; + this.lineNr = lineNr; + this.systemID = systemID; + this.parent = null; } @@ -479,316 +228,330 @@ public class XMLElement */ public XMLElement(PApplet parent, String filename) { this(); - try { - Reader r = parent.createReader(filename); +// try { + Reader r = parent.createReader(filename); //if (r == null) return; - parseFromReader(r); - } catch (IOException e) { - e.printStackTrace(); - } + parseFromReader(r); + //} catch (IOException e) { +// e.printStackTrace(); +// } } - public XMLElement(Reader r) throws IOException { + public XMLElement(Reader r) { this(); parseFromReader(r); } - - - public XMLElement(String s) { - this(); - + + + public XMLElement(String xml) { + this(); + StringReader r = new StringReader(xml); + parseFromReader(r); + } + + + protected void parseFromReader(Reader r) { try { - parseString(s); - } catch (XMLParseException e) { - e.printStackTrace(); - } + StdXMLParser parser = new StdXMLParser(); + parser.setBuilder(new StdXMLBuilder(this)); + parser.setValidator(new XMLValidator()); + parser.setReader(new StdXMLReader(r)); + //System.out.println(parser.parse().getName()); + /*XMLElement xm = (XMLElement)*/ parser.parse(); + //System.out.println("xm name is " + xm.getName()); + //System.out.println(xm + " " + this); + //parser.parse(); + } catch (XMLException e) { + e.printStackTrace(); + } + } + + +// static public XMLElement parse(Reader r) { +// try { +// StdXMLParser parser = new StdXMLParser(); +// parser.setBuilder(new StdXMLBuilder()); +// parser.setValidator(new XMLValidator()); +// parser.setReader(new StdXMLReader(r)); +// return (XMLElement) parser.parse(); +// } catch (XMLException e) { +// e.printStackTrace(); +// return null; +// } +// } + + + /** + * Creates an element to be used for #PCDATA content. + */ + public XMLElement createPCDataElement() { + return new XMLElement(); } - public XMLElement(InputStream input) throws IOException { - this(new InputStreamReader(input, "UTF-8")); + /** + * Creates an empty element. + * + * @param fullName the name of the element. + */ +// public XMLElement createElement(String fullName) { +// return new XMLElement(fullName); +// } + + + /** + * Creates an empty element. + * + * @param fullName the name of the element. + * @param systemID the system ID of the XML data where the element starts. + * @param lineNr the line in the XML data where the element starts. + */ +// public XMLElement createElement(String fullName, +// String systemID, +// int lineNr) { +// //return new XMLElement(fullName, systemID, lineNr); +// return new XMLElement(fullName, null, systemID, lineNr); +// } + + + /** + * Creates an empty element. + * + * @param fullName the full name of the element + * @param namespace the namespace URI. + */ + public XMLElement createElement(String fullName, + String namespace) { + //return new XMLElement(fullName, namespace); + return new XMLElement(fullName, namespace, null, NO_LINE); + } + + + /** + * Creates an empty element. + * + * @param fullName the full name of the element + * @param namespace the namespace URI. + * @param systemID the system ID of the XML data where the element starts. + * @param lineNr the line in the XML data where the element starts. + */ + public XMLElement createElement(String fullName, + String namespace, + String systemID, + int lineNr) { + return new XMLElement(fullName, namespace, systemID, lineNr); + } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() throws Throwable { + this.attributes.clear(); + this.attributes = null; + this.children = null; + this.fullName = null; + this.name = null; + this.namespace = null; + this.content = null; + this.systemID = null; + this.parent = null; + super.finalize(); + } + + + /** + * Returns the parent element. This method returns null for the root + * element. + */ + public XMLElement getParent() { + return this.parent; + } + + + /** + * Returns the full name (i.e. the name including an eventual namespace + * prefix) of the element. + * + * @return the name, or null if the element only contains #PCDATA. + */ + public String getFullName() { + return this.fullName; + } + + + /** + * Returns the name of the element. + * + * @return the name, or null if the element only contains #PCDATA. + */ + public String getName() { + return this.name; + } + + + /** + * Returns the namespace of the element. + * + * @return the namespace, or null if no namespace is associated with the + * element. + */ + public String getNamespace() { + return this.namespace; + } + + + /** + * Sets the full name. This method also sets the short name and clears the + * namespace URI. + * + * @param name the non-null name. + */ + public void setName(String name) { + this.name = name; + this.fullName = name; + this.namespace = null; + } + + + /** + * Sets the name. + * + * @param fullName the non-null full name. + * @param namespace the namespace URI, which may be null. + */ + public void setName(String fullName, String namespace) { + int index = fullName.indexOf(':'); + if ((namespace == null) || (index < 0)) { + this.name = fullName; + } else { + this.name = fullName.substring(index + 1); + } + this.fullName = fullName; + this.namespace = namespace; } /** * Adds a child element. * - * @param child - * The child element to add. - * - *
Preconditions:
- *
  • child != null - *
  • child.getName() != null - *
  • child does not have a parent element - *
- * - *
Postconditions:
- *
  • countChildren() => old.countChildren() + 1 - *
  • enumerateChildren() => old.enumerateChildren() + child - *
  • getChildren() => old.enumerateChildren() + child - *
- * - * @see processing.xml.XMLElement#countChildren() - * @see processing.xml.XMLElement#enumerateChildren() - * @see processing.xml.XMLElement#getChildren() - * @see processing.xml.XMLElement#removeChild(processing.xml.XMLElement) - * removeChild(XMLElement) + * @param child the non-null child to add. */ - public void addChild(XMLElement child) - { + public void addChild(XMLElement child) { + if (child == null) { + throw new IllegalArgumentException("child must not be null"); + } + if ((child.getName() == null) && (! this.children.isEmpty())) { + XMLElement lastChild = (XMLElement) this.children.lastElement(); + + if (lastChild.getName() == null) { + lastChild.setContent(lastChild.getContent() + + child.getContent()); + return; + } + } + ((XMLElement)child).parent = this; this.children.addElement(child); } /** - * Adds or modifies an attribute. + * Inserts a child element. * - * @param name - * The name of the attribute. - * @param value - * The value of the attribute. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
  • value != null - *
- * - *
Postconditions:
- *
  • enumerateAttributeNames() - * => old.enumerateAttributeNames() + name - *
  • getAttribute(name) => value - *
- * - * @see processing.xml.XMLElement#setDoubleAttribute(java.lang.String, double) - * setDoubleAttribute(String, double) - * @see processing.xml.XMLElement#setIntAttribute(java.lang.String, int) - * setIntAttribute(String, int) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getAttribute(java.lang.String) - * getAttribute(String) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, java.lang.Object) - * getAttribute(String, Object) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String) - * getStringAttribute(String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.lang.String) - * getStringAttribute(String, String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getStringAttribute(String, Hashtable, String, boolean) + * @param child the non-null child to add. + * @param index where to put the child. */ - public void setAttribute(String name, - Object value) - { - if (this.ignoreCase) { - name = name.toUpperCase(); + public void insertChild(XMLElement child, int index) { + if (child == null) { + throw new IllegalArgumentException("child must not be null"); } - this.attributes.put(name, value.toString()); - } - - - /** - * Adds or modifies an attribute. - * - * @param name - * The name of the attribute. - * @param value - * The value of the attribute. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - *
Postconditions:
- *
  • enumerateAttributeNames() - * => old.enumerateAttributeNames() + name - *
  • getIntAttribute(name) => value - *
- * - * @see processing.xml.XMLElement#setDoubleAttribute(java.lang.String, double) - * setDoubleAttribute(String, double) - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String) - * getIntAttribute(String) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, int) - * getIntAttribute(String, int) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getIntAttribute(String, Hashtable, String, boolean) - */ - public void setIntAttribute(String name, - int value) - { - if (this.ignoreCase) { - name = name.toUpperCase(); + if ((child.getName() == null) && (! this.children.isEmpty())) { + XMLElement lastChild = (XMLElement) this.children.lastElement(); + if (lastChild.getName() == null) { + lastChild.setContent(lastChild.getContent() + + child.getContent()); + return; + } } - this.attributes.put(name, Integer.toString(value)); + ((XMLElement) child).parent = this; + this.children.insertElementAt(child, index); } /** - * Adds or modifies an attribute. + * Removes a child element. * - * @param name - * The name of the attribute. - * @param value - * The value of the attribute. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - *
Postconditions:
- *
  • enumerateAttributeNames() - * => old.enumerateAttributeNames() + name - *
  • getDoubleAttribute(name) => value - *
- * - * @see processing.xml.XMLElement#setIntAttribute(java.lang.String, int) - * setIntAttribute(String, int) - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String) - * getDoubleAttribute(String) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, double) - * getDoubleAttribute(String, double) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getDoubleAttribute(String, Hashtable, String, boolean) + * @param child the non-null child to remove. */ - public void setDoubleAttribute(String name, - double value) - { - if (this.ignoreCase) { - name = name.toUpperCase(); + public void removeChild(XMLElement child) { + if (child == null) { + throw new IllegalArgumentException("child must not be null"); } - this.attributes.put(name, Double.toString(value)); + this.children.removeElement(child); } /** - * Get the number of children for this element. - * @return number of children + * Removes the child located at a certain index. + * + * @param index the index of the child, where the first child has index 0. */ - public int getChildCount() - { - return this.children.size(); + public void removeChildAtIndex(int index) { + this.children.removeElementAt(index); } /** - * Enumerates the attribute names. + * Returns an enumeration of all child elements. * - *
Postconditions:
- *
  • result != null - *
- * - * @see processing.xml.XMLElement#setDoubleAttribute(java.lang.String, double) - * setDoubleAttribute(String, double) - * @see processing.xml.XMLElement#setIntAttribute(java.lang.String, int) - * setIntAttribute(String, int) - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#getAttribute(java.lang.String) - * getAttribute(String) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, java.lang.Object) - * getAttribute(String, String) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String) - * getStringAttribute(String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.lang.String) - * getStringAttribute(String, String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getStringAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String) - * getIntAttribute(String) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, int) - * getIntAttribute(String, int) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getIntAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String) - * getDoubleAttribute(String) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, double) - * getDoubleAttribute(String, double) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getDoubleAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getBooleanAttribute(java.lang.String, - * java.lang.String, - * java.lang.String, boolean) - * getBooleanAttribute(String, String, String, boolean) + * @return the non-null enumeration */ - public Enumeration enumerateAttributeNames() - { - return this.attributes.keys(); - } - - - /** - * Enumerates the child elements. - * - *
Postconditions:
- *
  • result != null - *
- * - * @see processing.xml.XMLElement#addChild(processing.xml.XMLElement) - * addChild(XMLElement) - * @see processing.xml.XMLElement#countChildren() - * @see processing.xml.XMLElement#getChildren() - * @see processing.xml.XMLElement#removeChild(processing.xml.XMLElement) - * removeChild(XMLElement) - */ - public Enumeration enumerateChildren() - { + public Enumeration enumerateChildren() { return this.children.elements(); } /** - * Returns the child elements as an array. The code currently uses - * a Vector internally, but that will likely change in a future release - * because arrays are more efficient. + * Returns whether the element is a leaf element. * - *
Postconditions:
- *
  • result != null - *
- * - * @see processing.xml.XMLElement#addChild(processing.xml.XMLElement) - * addChild(XMLElement) - * @see processing.xml.XMLElement#countChildren() - * @see processing.xml.XMLElement#enumerateChildren() - * @see processing.xml.XMLElement#removeChild(processing.xml.XMLElement) - * removeChild(XMLElement) + * @return true if the element has no children. */ + public boolean isLeaf() { + return this.children.isEmpty(); + } + + + /** + * Returns whether the element has children. + * + * @return true if the element has children. + */ + public boolean hasChildren() { + return (! this.children.isEmpty()); + } + + + /** + * Returns the number of children. + * + * @return the count. + */ + public int getChildCount() { + return this.children.size(); + } + + + /** + * Returns a vector containing all the child elements. + * + * @return the vector. + */ +// public Vector getChildren() { +// return this.children; +// } + + public XMLElement[] getChildren() { int childCount = getChildCount(); XMLElement[] kids = new XMLElement[childCount]; @@ -814,7 +577,7 @@ public class XMLElement */ public XMLElement getChild(String name) { if (name.indexOf('/') != -1) { - return getChild(PApplet.split(name, '/'), 0); + return getChildRecursive(PApplet.split(name, '/'), 0); } int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -827,221 +590,540 @@ public class XMLElement } - protected XMLElement getChild(String[] items, int offset) { - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - XMLElement kid = getChild(i); - if (kid.getName().equals(items[offset])) { - if (offset == items.length-1) { - return kid; - } else { - return kid.getChild(items, offset+1); - } - } - } - return null; + protected XMLElement getChildRecursive(String[] items, int offset) { + // if it's a number, do an index instead + if (Character.isDigit(items[offset].charAt(0))) { + XMLElement kid = getChild(Integer.parseInt(items[offset])); + if (offset == items.length-1) { + return kid; + } else { + return kid.getChildRecursive(items, offset+1); + } + } + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + XMLElement kid = getChild(i); + if (kid.getName().equals(items[offset])) { + if (offset == items.length-1) { + return kid; + } else { + return kid.getChildRecursive(items, offset+1); + } + } + } + return null; } + + + /** + * Returns the child at a specific index. + * + * @param index the index of the child + * + * @return the non-null child + * + * @throws java.lang.ArrayIndexOutOfBoundsException + * if the index is out of bounds. + */ + public XMLElement getChildAtIndex(int index) + throws ArrayIndexOutOfBoundsException { + return (XMLElement) this.children.elementAt(index); + } + + + /** + * Searches a child element. + * + * @param name the full name of the child to search for. + * + * @return the child element, or null if no such child was found. + */ +// public XMLElement getFirstChildNamed(String name) { +// Enumeration enum = this.children.elements(); +// while (enum.hasMoreElements()) { +// XMLElement child = (XMLElement) enum.nextElement(); +// String childName = child.getFullName(); +// if ((childName != null) && childName.equals(name)) { +// return child; +// } +// } +// return null; +// } + + + /** + * Searches a child element. + * + * @param name the name of the child to search for. + * @param namespace the namespace, which may be null. + * + * @return the child element, or null if no such child was found. + */ +// public XMLElement getFirstChildNamed(String name, +// String namespace) { +// Enumeration enum = this.children.elements(); +// while (enum.hasMoreElements()) { +// XMLElement child = (XMLElement) enum.nextElement(); +// String str = child.getName(); +// boolean found = (str != null) && (str.equals(name)); +// str = child.getNamespace(); +// if (str == null) { +// found &= (name == null); +// } else { +// found &= str.equals(namespace); +// } +// if (found) { +// return child; +// } +// } +// return null; +// } /** * Get any children that match this name or path. Similar to getChild(), * but will grab multiple matches rather than only the first. * @param name element name or path/to/element - * @return + * @return array of child elements that match * @author processing.org */ public XMLElement[] getChildren(String name) { - if (name.indexOf('/') != -1) { - return getChildren(PApplet.split(name, '/'), 0); - } - int childCount = getChildCount(); - XMLElement[] matches = new XMLElement[childCount]; - int matchCount = 0; - for (int i = 0; i < childCount; i++) { - XMLElement kid = getChild(i); - if (kid.getName().equals(name)) { - matches[matchCount++] = kid; - } - } - return (XMLElement[]) PApplet.subset(matches, 0, matchCount); + if (name.indexOf('/') != -1) { + return getChildrenRecursive(PApplet.split(name, '/'), 0); + } + // if it's a number, do an index instead + // (returns a single element array, since this will be a single match + if (Character.isDigit(name.charAt(0))) { + return new XMLElement[] { getChild(Integer.parseInt(name)) }; + } + int childCount = getChildCount(); + XMLElement[] matches = new XMLElement[childCount]; + int matchCount = 0; + for (int i = 0; i < childCount; i++) { + XMLElement kid = getChild(i); + if (kid.getName().equals(name)) { + matches[matchCount++] = kid; + } + } + return (XMLElement[]) PApplet.subset(matches, 0, matchCount); } - protected XMLElement[] getChildren(String[] items, int offset) { + protected XMLElement[] getChildrenRecursive(String[] items, int offset) { if (offset == items.length-1) { - return getChildren(items[offset]); + return getChildren(items[offset]); } XMLElement[] matches = getChildren(items[offset]); XMLElement[] outgoing = new XMLElement[0]; for (int i = 0; i < matches.length; i++) { - XMLElement[] kidMatches = matches[i].getChildren(items, offset+1); - outgoing = (XMLElement[]) PApplet.concat(outgoing, kidMatches); + XMLElement[] kidMatches = matches[i].getChildrenRecursive(items, offset+1); + outgoing = (XMLElement[]) PApplet.concat(outgoing, kidMatches); } return outgoing; } /** - * Returns the PCDATA content of the object. If there is no such content, - * null is returned. + * Returns a vector of all child elements named name. * - * @see processing.xml.XMLElement#setContent(java.lang.String) - * setContent(String) + * @param name the full name of the children to search for. + * + * @return the non-null vector of child elements. */ - public String getContent() - { - return this.content; - } +// public Vector getChildrenNamed(String name) { +// Vector result = new Vector(this.children.size()); +// Enumeration enum = this.children.elements(); +// while (enum.hasMoreElements()) { +// XMLElement child = (XMLElement) enum.nextElement(); +// String childName = child.getFullName(); +// if ((childName != null) && childName.equals(name)) { +// result.addElement(child); +// } +// } +// return result; +// } /** - * Returns the line Number in the source data on which the element is found. - * This method returns 0 there is no associated source data. + * Returns a vector of all child elements named name. * - *
Postconditions:
- *
  • result >= 0 - *
+ * @param name the name of the children to search for. + * @param namespace the namespace, which may be null. + * + * @return the non-null vector of child elements. */ - public int getLineNumber() - { - return this.lineNumber; - } +// public Vector getChildrenNamed(String name, +// String namespace) { +// Vector result = new Vector(this.children.size()); +// Enumeration enum = this.children.elements(); +// while (enum.hasMoreElements()) { +// XMLElement child = (XMLElement) enum.nextElement(); +// String str = child.getName(); +// boolean found = (str != null) && (str.equals(name)); +// str = child.getNamespace(); +// if (str == null) { +// found &= (name == null); +// } else { +// found &= str.equals(namespace); +// } +// +// if (found) { +// result.addElement(child); +// } +// } +// return result; +// } /** - * Convenience function to check whether an attribute is available. - * @author fry - * @param name name of the attribute - * @return true if the attribute is non-null + * Searches an attribute. + * + * @param fullName the non-null full name of the attribute. + * + * @return the attribute, or null if the attribute does not exist. */ - public boolean hasAttribute(String aname) { - return (getAttribute(aname) != null); - } - - - /** - * Returns an attribute of the element. - * If the attribute doesn't exist, null is returned. - * - * @param name The name of the attribute. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getAttribute(java.lang.String, java.lang.Object) - * getAttribute(String, Object) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getAttribute(String, Hashtable, String, boolean) - */ - public Object getAttribute(String aname) - { - return this.getAttribute(aname, null); - } - - - /** - * Returns an attribute of the element. - * If the attribute doesn't exist, defaultValue is returned. - * - * @param aname The name of the attribute. - * @param defaultValue Key to use if the attribute is missing. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getAttribute(java.lang.String) - * getAttribute(String) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getAttribute(String, Hashtable, String, boolean) - */ - public Object getAttribute(String aname, - Object defaultValue) - { - if (this.ignoreCase) { - aname = aname.toUpperCase(); + private XMLAttribute findAttribute(String fullName) { + Enumeration en = this.attributes.elements(); + while (en.hasMoreElements()) { + XMLAttribute attr = (XMLAttribute) en.nextElement(); + if (attr.getFullName().equals(fullName)) { + return attr; + } } - Object value = this.attributes.get(aname); - if (value == null) { - value = defaultValue; - } - return value; + return null; } /** - * Returns an attribute by looking up a key in a hashtable. - * If the attribute doesn't exist, the value corresponding to defaultKey - * is returned. - *

- * As an example, if valueSet contains the mapping "one" => - * "1" - * and the element contains the attribute attr="one", then - * getAttribute("attr", mapping, defaultKey, false) returns - * "1". + * Searches an attribute. * - * @param name - * The name of the attribute. - * @param valueSet - * Hashtable mapping keys to values. - * @param defaultKey - * Key to use if the attribute is missing. - * @param allowLiterals - * true if literals are valid. + * @param name the non-null short name of the attribute. + * @param namespace the name space, which may be null. * - *

Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
  • valueSet != null - *
  • the keys of valueSet are strings - *
- * - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getAttribute(java.lang.String) - * getAttribute(String) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, java.lang.Object) - * getAttribute(String, Object) + * @return the attribute, or null if the attribute does not exist. */ - public Object getAttribute(String name, - Hashtable valueSet, - String defaultKey, - boolean allowLiterals) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - Object key = this.attributes.get(name); - Object result; - if (key == null) { - key = defaultKey; - } - result = valueSet.get(key); - if (result == null) { - if (allowLiterals) { - result = key; + private XMLAttribute findAttribute(String name, + String namespace) { + Enumeration en = this.attributes.elements(); + while (en.hasMoreElements()) { + XMLAttribute attr = (XMLAttribute) en.nextElement(); + boolean found = attr.getName().equals(name); + if (namespace == null) { + found &= (attr.getNamespace() == null); } else { - throw this.invalidValue(name, (String) key); + found &= namespace.equals(attr.getNamespace()); + } + + if (found) { + return attr; + } + } + return null; + } + + + /** + * Returns the number of attributes. + */ + public int getAttributeCount() { + return this.attributes.size(); + } + + + /** + * @deprecated As of NanoXML/Java 2.1, replaced by + * {@link #getAttribute(java.lang.String,java.lang.String)} + * Returns the value of an attribute. + * + * @param name the non-null name of the attribute. + * + * @return the value, or null if the attribute does not exist. + */ + public String getAttribute(String name) { + return this.getAttribute(name, null); + } + + + /** + * Returns the value of an attribute. + * + * @param name the non-null full name of the attribute. + * @param defaultValue the default value of the attribute. + * + * @return the value, or defaultValue if the attribute does not exist. + */ + public String getAttribute(String name, + String defaultValue) { + XMLAttribute attr = this.findAttribute(name); + if (attr == null) { + return defaultValue; + } else { + return attr.getValue(); + } + } + + + /** + * Returns the value of an attribute. + * + * @param name the non-null name of the attribute. + * @param namespace the namespace URI, which may be null. + * @param defaultValue the default value of the attribute. + * + * @return the value, or defaultValue if the attribute does not exist. + */ + public String getAttribute(String name, + String namespace, + String defaultValue) { + XMLAttribute attr = this.findAttribute(name, namespace); + if (attr == null) { + return defaultValue; + } else { + return attr.getValue(); + } + } + + + /** + * Returns the value of an attribute. + * + * @param name the non-null full name of the attribute. + * @param defaultValue the default value of the attribute. + * + * @return the value, or defaultValue if the attribute does not exist. + */ + public int getAttribute(String name, + int defaultValue) { + String value = this.getAttribute(name, Integer.toString(defaultValue)); + return Integer.parseInt(value); + } + + + /** + * Returns the value of an attribute. + * + * @param name the non-null name of the attribute. + * @param namespace the namespace URI, which may be null. + * @param defaultValue the default value of the attribute. + * + * @return the value, or defaultValue if the attribute does not exist. + */ + public int getAttribute(String name, + String namespace, + int defaultValue) { + String value = this.getAttribute(name, namespace, + Integer.toString(defaultValue)); + return Integer.parseInt(value); + } + + + /** + * Returns the type of an attribute. + * + * @param name the non-null full name of the attribute. + * + * @return the type, or null if the attribute does not exist. + */ + public String getAttributeType(String name) { + XMLAttribute attr = this.findAttribute(name); + if (attr == null) { + return null; + } else { + return attr.getType(); + } + } + + + /** + * Returns the namespace of an attribute. + * + * @param name the non-null full name of the attribute. + * + * @return the namespace, or null if there is none associated. + */ + public String getAttributeNamespace(String name) { + XMLAttribute attr = this.findAttribute(name); + if (attr == null) { + return null; + } else { + return attr.getNamespace(); + } + } + + + /** + * Returns the type of an attribute. + * + * @param name the non-null name of the attribute. + * @param namespace the namespace URI, which may be null. + * + * @return the type, or null if the attribute does not exist. + */ + public String getAttributeType(String name, + String namespace) { + XMLAttribute attr = this.findAttribute(name, namespace); + if (attr == null) { + return null; + } else { + return attr.getType(); + } + } + + + /** + * Sets an attribute. + * + * @param name the non-null full name of the attribute. + * @param value the non-null value of the attribute. + */ + public void setAttribute(String name, + String value) { + XMLAttribute attr = this.findAttribute(name); + if (attr == null) { + attr = new XMLAttribute(name, name, null, value, "CDATA"); + this.attributes.addElement(attr); + } else { + attr.setValue(value); + } + } + + + /** + * Sets an attribute. + * + * @param fullName the non-null full name of the attribute. + * @param namespace the namespace URI of the attribute, which may be null. + * @param value the non-null value of the attribute. + */ + public void setAttribute(String fullName, + String namespace, + String value) { + int index = fullName.indexOf(':'); + String name = fullName.substring(index + 1); + XMLAttribute attr = this.findAttribute(name, namespace); + if (attr == null) { + attr = new XMLAttribute(fullName, name, namespace, value, "CDATA"); + this.attributes.addElement(attr); + } else { + attr.setValue(value); + } + } + + + /** + * Removes an attribute. + * + * @param name the non-null name of the attribute. + */ + public void removeAttribute(String name) { + for (int i = 0; i < this.attributes.size(); i++) { + XMLAttribute attr = (XMLAttribute) this.attributes.elementAt(i); + if (attr.getFullName().equals(name)) { + this.attributes.removeElementAt(i); + return; + } + } + } + + + /** + * Removes an attribute. + * + * @param name the non-null name of the attribute. + * @param namespace the namespace URI of the attribute, which may be null. + */ + public void removeAttribute(String name, + String namespace) { + for (int i = 0; i < this.attributes.size(); i++) { + XMLAttribute attr = (XMLAttribute) this.attributes.elementAt(i); + boolean found = attr.getName().equals(name); + if (namespace == null) { + found &= (attr.getNamespace() == null); + } else { + found &= attr.getNamespace().equals(namespace); + } + + if (found) { + this.attributes.removeElementAt(i); + return; + } + } + } + + + /** + * Returns an enumeration of all attribute names. + * + * @return the non-null enumeration. + */ + public Enumeration enumerateAttributeNames() { + Vector result = new Vector(); + Enumeration en = this.attributes.elements(); + while (en.hasMoreElements()) { + XMLAttribute attr = (XMLAttribute) en.nextElement(); + result.addElement(attr.getFullName()); + } + return result.elements(); + } + + + /** + * Returns whether an attribute exists. + * + * @return true if the attribute exists. + */ + public boolean hasAttribute(String name) { + return this.findAttribute(name) != null; + } + + + /** + * Returns whether an attribute exists. + * + * @return true if the attribute exists. + */ + public boolean hasAttribute(String name, + String namespace) { + return this.findAttribute(name, namespace) != null; + } + + + /** + * Returns all attributes as a Properties object. + * + * @return the non-null set. + */ + public Properties getAttributes() { + Properties result = new Properties(); + Enumeration en = this.attributes.elements(); + while (en.hasMoreElements()) { + XMLAttribute attr = (XMLAttribute) en.nextElement(); + result.put(attr.getFullName(), attr.getValue()); + } + return result; + } + + + /** + * Returns all attributes in a specific namespace as a Properties object. + * + * @param namespace the namespace URI of the attributes, which may be null. + * + * @return the non-null set. + */ + public Properties getAttributesInNamespace(String namespace) { + Properties result = new Properties(); + Enumeration en = this.attributes.elements(); + while (en.hasMoreElements()) { + XMLAttribute attr = (XMLAttribute) en.nextElement(); + if (namespace == null) { + if (attr.getNamespace() == null) { + result.put(attr.getName(), attr.getValue()); + } + } else { + if (namespace.equals(attr.getNamespace())) { + result.put(attr.getName(), attr.getValue()); + } } } return result; @@ -1049,1842 +1131,122 @@ public class XMLElement /** - * Returns an attribute of the element. - * If the attribute doesn't exist, null is returned. + * Returns the system ID of the data where the element started. * - * @param name The name of the attribute. + * @return the system ID, or null if unknown. * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.lang.String) - * getStringAttribute(String, String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getStringAttribute(String, Hashtable, String, boolean) + * @see #getLineNr */ - public String getStringAttribute(String name) - { - return this.getStringAttribute(name, null); + public String getSystemID() { + return this.systemID; } /** - * Returns an attribute of the element. - * If the attribute doesn't exist, defaultValue is returned. + * Returns the line number in the data where the element started. * - * @param name The name of the attribute. - * @param defaultValue Key to use if the attribute is missing. + * @return the line number, or NO_LINE if unknown. * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String) - * getStringAttribute(String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getStringAttribute(String, Hashtable, String, boolean) + * @see #NO_LINE + * @see #getSystemID */ - public String getStringAttribute(String name, - String defaultValue) - { - return (String) this.getAttribute(name, defaultValue); + public int getLineNr() { + return this.lineNr; } /** - * Returns an attribute by looking up a key in a hashtable. - * If the attribute doesn't exist, the value corresponding to defaultKey - * is returned. - *

- * As an example, if valueSet contains the mapping "one" => - * "1" - * and the element contains the attribute attr="one", then - * getAttribute("attr", mapping, defaultKey, false) returns - * "1". + * Return the #PCDATA content of the element. If the element has a + * combination of #PCDATA content and child elements, the #PCDATA + * sections can be retrieved as unnamed child objects. In this case, + * this method returns null. * - * @param name - * The name of the attribute. - * @param valueSet - * Hashtable mapping keys to values. - * @param defaultKey - * Key to use if the attribute is missing. - * @param allowLiterals - * true if literals are valid. - * - *

Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
  • valueSet != null - *
  • the keys of valueSet are strings - *
  • the values of valueSet are strings - *
- * - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String) - * getStringAttribute(String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.lang.String) - * getStringAttribute(String, String) + * @return the content. */ - public String getStringAttribute(String name, - Hashtable valueSet, - String defaultKey, - boolean allowLiterals) - { - return (String) this.getAttribute(name, valueSet, defaultKey, - allowLiterals); + public String getContent() { + return this.content; } /** - * Returns an attribute of the element. - * If the attribute doesn't exist, 0 is returned. + * Sets the #PCDATA content. It is an error to call this method with a + * non-null value if there are child objects. * - * @param name The name of the attribute. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setIntAttribute(java.lang.String, int) - * setIntAttribute(String, int) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, int) - * getIntAttribute(String, int) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getIntAttribute(String, Hashtable, String, boolean) + * @param content the (possibly null) content. */ - public int getIntAttribute(String name) - { - return this.getIntAttribute(name, 0); - } - - - /** - * Returns an attribute of the element. - * If the attribute doesn't exist, defaultValue is returned. - * - * @param name The name of the attribute. - * @param defaultValue Key to use if the attribute is missing. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setIntAttribute(java.lang.String, int) - * setIntAttribute(String, int) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String) - * getIntAttribute(String) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getIntAttribute(String, Hashtable, String, boolean) - */ - public int getIntAttribute(String name, - int defaultValue) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - String value = (String) this.attributes.get(name); - if (value == null) { - return defaultValue; - } else { - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw this.invalidValue(name, value); - } - } - } - - - /** - * Returns an attribute by looking up a key in a hashtable. - * If the attribute doesn't exist, the value corresponding to defaultKey - * is returned. - *

- * As an example, if valueSet contains the mapping "one" => 1 - * and the element contains the attribute attr="one", then - * getIntAttribute("attr", mapping, defaultKey, false) returns - * 1. - * - * @param name - * The name of the attribute. - * @param valueSet - * Hashtable mapping keys to values. - * @param defaultKey - * Key to use if the attribute is missing. - * @param allowLiteralNumbers - * true if literal numbers are valid. - * - *

Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
  • valueSet != null - *
  • the keys of valueSet are strings - *
  • the values of valueSet are Integer objects - *
  • defaultKey is either null, a - * key in valueSet or an integer. - *
- * - * @see processing.xml.XMLElement#setIntAttribute(java.lang.String, int) - * setIntAttribute(String, int) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String) - * getIntAttribute(String) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, int) - * getIntAttribute(String, int) - */ - public int getIntAttribute(String name, - Hashtable valueSet, - String defaultKey, - boolean allowLiteralNumbers) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - Object key = this.attributes.get(name); - Integer result; - if (key == null) { - key = defaultKey; - } - try { - result = (Integer) valueSet.get(key); - } catch (ClassCastException e) { - throw this.invalidValueSet(name); - } - if (result == null) { - if (! allowLiteralNumbers) { - throw this.invalidValue(name, (String) key); - } - try { - result = Integer.valueOf((String) key); - } catch (NumberFormatException e) { - throw this.invalidValue(name, (String) key); - } - } - return result.intValue(); - } - - - /** - * Method to get a float attribute, identical to getDoubleAttribute(), - * @see processing.xml.XMLElement#getDoubleAttribute() but added to - * handle float data as used by Processing. This code is adapted from - * the identical code in the getDoubleAttribute methods. - * @author processing.org - */ - public float getFloatAttribute(String name) - { - return getFloatAttribute(name, 0); - } - - - /** - * Method to get a float attribute, identical to getDoubleAttribute(), - * @see processing.xml.XMLElement#getDoubleAttribute() but added to - * handle float data as used by Processing. This code is adapted from - * the identical code in the getDoubleAttribute methods. - * @author processing.org - */ - public float getFloatAttribute(String name, - float defaultValue) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - String value = (String) this.attributes.get(name); - if (value == null) { - return defaultValue; - } else { - try { - return Float.valueOf(value).floatValue(); - } catch (NumberFormatException e) { - throw this.invalidValue(name, value); - } - } - } - - - /** - * Method to get a float attribute, identical to getDoubleAttribute(), - * @see processing.xml.XMLElement#getDoubleAttribute() but added to - * handle float data as used by Processing. This code is adapted from - * the identical code in the getDoubleAttribute methods. - * @author processing.org - */ - public float getFloatAttribute(String name, - Hashtable valueSet, - String defaultKey, - boolean allowLiteralNumbers) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - Object key = this.attributes.get(name); - Float result; - if (key == null) { - key = defaultKey; - } - try { - result = (Float) valueSet.get(key); - } catch (ClassCastException e) { - throw this.invalidValueSet(name); - } - if (result == null) { - if (! allowLiteralNumbers) { - throw this.invalidValue(name, (String) key); - } - try { - result = Float.valueOf((String) key); - } catch (NumberFormatException e) { - throw this.invalidValue(name, (String) key); - } - } - return result.floatValue(); - } - - - /** - * Returns an attribute of the element. - * If the attribute doesn't exist, 0.0 is returned. - * - * @param name The name of the attribute. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setDoubleAttribute(java.lang.String, double) - * setDoubleAttribute(String, double) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, double) - * getDoubleAttribute(String, double) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getDoubleAttribute(String, Hashtable, String, boolean) - */ - public double getDoubleAttribute(String name) - { - return this.getDoubleAttribute(name, 0.); - } - - - /** - * Returns an attribute of the element. - * If the attribute doesn't exist, defaultValue is returned. - * - * @param name The name of the attribute. - * @param defaultValue Key to use if the attribute is missing. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#setDoubleAttribute(java.lang.String, double) - * setDoubleAttribute(String, double) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String) - * getDoubleAttribute(String) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getDoubleAttribute(String, Hashtable, String, boolean) - */ - public double getDoubleAttribute(String name, - double defaultValue) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - String value = (String) this.attributes.get(name); - if (value == null) { - return defaultValue; - } else { - try { - return Double.valueOf(value).doubleValue(); - } catch (NumberFormatException e) { - throw this.invalidValue(name, value); - } - } - } - - - /** - * Returns an attribute by looking up a key in a hashtable. - * If the attribute doesn't exist, the value corresponding to defaultKey - * is returned. - *

- * As an example, if valueSet contains the mapping "one" => - * 1.0 - * and the element contains the attribute attr="one", then - * getDoubleAttribute("attr", mapping, defaultKey, false) - * returns 1.0. - * - * @param name - * The name of the attribute. - * @param valueSet - * Hashtable mapping keys to values. - * @param defaultKey - * Key to use if the attribute is missing. - * @param allowLiteralNumbers - * true if literal numbers are valid. - * - *

Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
  • valueSet != null - *
  • the keys of valueSet are strings - *
  • the values of valueSet are Double objects - *
  • defaultKey is either null, a - * key in valueSet or a double. - *
- * - * @see processing.xml.XMLElement#setDoubleAttribute(java.lang.String, double) - * setDoubleAttribute(String, double) - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String) - * getDoubleAttribute(String) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, double) - * getDoubleAttribute(String, double) - */ - public double getDoubleAttribute(String name, - Hashtable valueSet, - String defaultKey, - boolean allowLiteralNumbers) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - Object key = this.attributes.get(name); - Double result; - if (key == null) { - key = defaultKey; - } - try { - result = (Double) valueSet.get(key); - } catch (ClassCastException e) { - throw this.invalidValueSet(name); - } - if (result == null) { - if (! allowLiteralNumbers) { - throw this.invalidValue(name, (String) key); - } - try { - result = Double.valueOf((String) key); - } catch (NumberFormatException e) { - throw this.invalidValue(name, (String) key); - } - } - return result.doubleValue(); - } - - - /** - * Returns an attribute of the element. - * If the attribute doesn't exist, defaultValue is returned. - * If the value of the attribute is equal to trueValue, - * true is returned. - * If the value of the attribute is equal to falseValue, - * false is returned. - * If the value doesn't match trueValue or - * falseValue, an exception is thrown. - * - * @param name The name of the attribute. - * @param trueValue The value associated with true. - * @param falseValue The value associated with true. - * @param defaultValue Value to use if the attribute is missing. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
  • trueValue and falseValue - * are different strings. - *
- * - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#removeAttribute(java.lang.String) - * removeAttribute(String) - * @see processing.xml.XMLElement#enumerateAttributeNames() - */ - public boolean getBooleanAttribute(String name, - String trueValue, - String falseValue, - boolean defaultValue) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - Object value = this.attributes.get(name); - if (value == null) { - return defaultValue; - } else if (value.equals(trueValue)) { - return true; - } else if (value.equals(falseValue)) { - return false; - } else { - throw this.invalidValue(name, (String) value); - } - } - - - /** - * Returns the name of the element. - * - * @see processing.xml.XMLElement#setName(java.lang.String) setName(String) - */ - public String getName() - { - return this.name; - } - - - /** - * Reads one XML element from a java.io.Reader and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * - *
Preconditions:
- *
  • reader != null - *
  • reader is not closed - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
  • the reader points to the first character following the last - * '>' character of the XML element - *
- * - * @throws java.io.IOException - * If an error occured while reading the input. - * @throws processing.xml.XMLParseException - * If an error occured while parsing the read data. - */ - public void parseFromReader(Reader r) - throws IOException, XMLParseException - { - this.parseFromReader(r, /*startingLineNumber*/ 1); - } - - - /** - * Reads one XML element from a java.io.Reader and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * @param startingLineNumber - * The line number of the first line in the data. - * - *
Preconditions:
- *
  • reader != null - *
  • reader is not closed - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
  • the reader points to the first character following the last - * '>' character of the XML element - *
- * - * @throws java.io.IOException - * If an error occured while reading the input. - * @throws processing.xml.XMLParseException - * If an error occured while parsing the read data. - */ - public void parseFromReader(Reader r, - int startingLineNumber) - throws IOException, XMLParseException - { - this.name = null; - this.content = ""; - this.attributes = new Hashtable(); - this.children = new Vector(); - this.charReadTooMuch = '\0'; - this.reader = r; - this.parserLineNumber = startingLineNumber; - - for (;;) { - char ch = this.scanWhitespace(); - - if (ch != '<') { - throw this.expectedInput("<"); - } - - ch = this.readChar(); - - if ((ch == '!') || (ch == '?')) { - this.skipSpecialTag(0); - } else { - this.unreadChar(ch); - this.scanElement(this); - return; - } - } - } - - - /** - * Reads one XML element from a String and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * - *
Preconditions:
- *
  • string != null - *
  • string.length() > 0 - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
- * - * @throws processing.xml.XMLParseException - * If an error occured while parsing the string. - */ - public void parseString(String string) throws XMLParseException - { - try { - this.parseFromReader(new StringReader(string), - /*startingLineNumber*/ 1); - } catch (IOException e) { - // Java exception handling suxx - } - } - - - /** - * Reads one XML element from a String and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * @param offset - * The first character in string to scan. - * - *
Preconditions:
- *
  • string != null - *
  • offset < string.length() - *
  • offset >= 0 - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
- * - * @throws processing.xml.XMLParseException - * If an error occured while parsing the string. - */ - public void parseString(String string, - int offset) - throws XMLParseException - { - this.parseString(string.substring(offset)); - } - - - /** - * Reads one XML element from a String and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * @param offset - * The first character in string to scan. - * @param end - * The character where to stop scanning. - * This character is not scanned. - * - *
Preconditions:
- *
  • string != null - *
  • end <= string.length() - *
  • offset < end - *
  • offset >= 0 - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
- * - * @throws processing.xml.XMLParseException - * If an error occured while parsing the string. - */ - public void parseString(String string, - int offset, - int end) - throws XMLParseException - { - this.parseString(string.substring(offset, end)); - } - - - /** - * Reads one XML element from a String and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * @param offset - * The first character in string to scan. - * @param end - * The character where to stop scanning. - * This character is not scanned. - * @param startingLineNumber - * The line number of the first line in the data. - * - *
Preconditions:
- *
  • string != null - *
  • end <= string.length() - *
  • offset < end - *
  • offset >= 0 - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
- * - * @throws processing.xml.XMLParseException - * If an error occured while parsing the string. - */ - public void parseString(String string, - int offset, - int end, - int startingLineNumber) - throws XMLParseException - { - string = string.substring(offset, end); - try { - this.parseFromReader(new StringReader(string), startingLineNumber); - } catch (IOException e) { - // Java exception handling suxx - } - } - - - /** - * Reads one XML element from a char array and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * @param offset - * The first character in string to scan. - * @param end - * The character where to stop scanning. - * This character is not scanned. - * - *
Preconditions:
- *
  • input != null - *
  • end <= input.length - *
  • offset < end - *
  • offset >= 0 - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
- * - * @throws processing.xml.XMLParseException - * If an error occured while parsing the string. - */ - public void parseCharArray(char[] input, - int offset, - int end) - throws XMLParseException - { - this.parseCharArray(input, offset, end, /*startingLineNumber*/ 1); - } - - - /** - * Reads one XML element from a char array and parses it. - * - * @param reader - * The reader from which to retrieve the XML data. - * @param offset - * The first character in string to scan. - * @param end - * The character where to stop scanning. - * This character is not scanned. - * @param startingLineNumber - * The line number of the first line in the data. - * - *
Preconditions:
- *
  • input != null - *
  • end <= input.length - *
  • offset < end - *
  • offset >= 0 - *
- * - *
Postconditions:
- *
  • the state of the receiver is updated to reflect the XML element - * parsed from the reader - *
- * - * @throws processing.xml.XMLParseException - * If an error occured while parsing the string. - */ - public void parseCharArray(char[] input, - int offset, - int end, - int startingLineNumber) - throws XMLParseException - { - try { - Reader r = new CharArrayReader(input, offset, end); - this.parseFromReader(r, startingLineNumber); - } catch (IOException e) { - // This exception will never happen. - } - } - - - /** - * Removes a child element. - * - * @param child - * The child element to remove. - * - *
Preconditions:
- *
  • child != null - *
  • child is a child element of the receiver - *
- * - *
Postconditions:
- *
  • countChildren() => old.countChildren() - 1 - *
  • enumerateChildren() => old.enumerateChildren() - child - *
  • getChildren() => old.enumerateChildren() - child - *
- * - * @see processing.xml.XMLElement#addChild(processing.xml.XMLElement) - * addChild(XMLElement) - * @see processing.xml.XMLElement#countChildren() - * @see processing.xml.XMLElement#enumerateChildren() - * @see processing.xml.XMLElement#getChildren() - */ - public void removeChild(XMLElement child) - { - this.children.removeElement(child); - } - - - /** - * Removes an attribute. - * - * @param name - * The name of the attribute. - * - *
Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - *
Postconditions:
- *
  • enumerateAttributeNames() - * => old.enumerateAttributeNames() - name - *
  • getAttribute(name) => null - *
- * - * @see processing.xml.XMLElement#enumerateAttributeNames() - * @see processing.xml.XMLElement#setDoubleAttribute(java.lang.String, double) - * setDoubleAttribute(String, double) - * @see processing.xml.XMLElement#setIntAttribute(java.lang.String, int) - * setIntAttribute(String, int) - * @see processing.xml.XMLElement#setAttribute(java.lang.String, java.lang.Object) - * setAttribute(String, Object) - * @see processing.xml.XMLElement#getAttribute(java.lang.String) - * getAttribute(String) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, java.lang.Object) - * getAttribute(String, Object) - * @see processing.xml.XMLElement#getAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String) - * getStringAttribute(String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.lang.String) - * getStringAttribute(String, String) - * @see processing.xml.XMLElement#getStringAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getStringAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String) - * getIntAttribute(String) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, int) - * getIntAttribute(String, int) - * @see processing.xml.XMLElement#getIntAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getIntAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String) - * getDoubleAttribute(String) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, double) - * getDoubleAttribute(String, double) - * @see processing.xml.XMLElement#getDoubleAttribute(java.lang.String, - * java.util.Hashtable, - * java.lang.String, boolean) - * getDoubleAttribute(String, Hashtable, String, boolean) - * @see processing.xml.XMLElement#getBooleanAttribute(java.lang.String, - * java.lang.String, - * java.lang.String, boolean) - * getBooleanAttribute(String, String, String, boolean) - */ - public void removeAttribute(String name) - { - if (this.ignoreCase) { - name = name.toUpperCase(); - } - this.attributes.remove(name); - } - - - /** - * Creates a new similar XML element. - *

- * You should override this method when subclassing XMLElement. - */ - protected XMLElement createAnotherElement() - { - return new XMLElement(this.entities, - this.ignoreWhitespace, - false, - this.ignoreCase); - } - - - /** - * Changes the content string. - * - * @param content - * The new content string. - */ - public void setContent(String content) - { + public void setContent(String content) { this.content = content; } /** - * Changes the name of the element. + * Returns true if the element equals another element. * - * @param name - * The new name. - * - *

Preconditions:
- *
  • name != null - *
  • name is a valid XML identifier - *
- * - * @see processing.xml.XMLElement#getName() + * @param rawElement the element to compare to */ - public void setName(String name) - { - this.name = name; - } - - - /** - * Writes the XML element to a string. - * - * @see processing.xml.XMLElement#write(java.io.Writer) write(Writer) - */ - public String toString() - { + public boolean equals(Object rawElement) { try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OutputStreamWriter writer = new OutputStreamWriter(out); - this.write(writer); - writer.flush(); - return new String(out.toByteArray()); - } catch (IOException e) { - // Java exception handling suxx - return super.toString(); - } - } - - - /** - * Writes the XML element to a writer. - * - * @param writer - * The writer to write the XML data to. - * - *
Preconditions:
- *
  • writer != null - *
  • writer is not closed - *
- * - * @throws java.io.IOException - * If the data could not be written to the writer. - * - * @see processing.xml.XMLElement#toString() - */ - public void write(Writer writer) - throws IOException - { - if (this.name == null) { - this.writeEncoded(writer, this.content); - return; - } - writer.write('<'); - writer.write(this.name); - if (! this.attributes.isEmpty()) { - Enumeration en = this.attributes.keys(); - while (en.hasMoreElements()) { - writer.write(' '); - String key = (String) en.nextElement(); - String value = (String) this.attributes.get(key); - writer.write(key); - writer.write('='); writer.write('"'); - this.writeEncoded(writer, value); - writer.write('"'); - } - } - if ((this.content != null) && (this.content.length() > 0)) { - writer.write('>'); - this.writeEncoded(writer, this.content); - writer.write('<'); writer.write('/'); - writer.write(this.name); - writer.write('>'); - } else if (this.children.isEmpty()) { - writer.write('/'); writer.write('>'); - } else { - writer.write('>'); - Enumeration en = this.enumerateChildren(); - while (en.hasMoreElements()) { - XMLElement child = (XMLElement) en.nextElement(); - child.write(writer); - } - writer.write('<'); writer.write('/'); - writer.write(this.name); - writer.write('>'); - } - } - - - /** - * Writes a string encoded to a writer. - * - * @param writer - * The writer to write the XML data to. - * @param str - * The string to write encoded. - * - *
Preconditions:
- *
  • writer != null - *
  • writer is not closed - *
  • str != null - *
- */ - protected void writeEncoded(Writer writer, - String str) - throws IOException - { - for (int i = 0; i < str.length(); i += 1) { - char ch = str.charAt(i); - switch (ch) { - case '<': - writer.write('&'); writer.write('l'); writer.write('t'); - writer.write(';'); - break; - case '>': - writer.write('&'); writer.write('g'); writer.write('t'); - writer.write(';'); - break; - case '&': - writer.write('&'); writer.write('a'); writer.write('m'); - writer.write('p'); writer.write(';'); - break; - case '"': - writer.write('&'); writer.write('q'); writer.write('u'); - writer.write('o'); writer.write('t'); writer.write(';'); - break; - case '\'': - writer.write('&'); writer.write('a'); writer.write('p'); - writer.write('o'); writer.write('s'); writer.write(';'); - break; - default: - int unicode = (int) ch; - if ((unicode < 32) || (unicode > 126)) { - writer.write('&'); writer.write('#'); - writer.write('x'); - writer.write(Integer.toString(unicode, 16)); - writer.write(';'); - } else { - writer.write(ch); - } - } - } - } - - - /** - * Scans an identifier from the current reader. - * The scanned identifier is appended to result. - * - * @param result - * The buffer in which the scanned identifier will be put. - * - *
Preconditions:
- *
  • result != null - *
  • The next character read from the reader is a valid first - * character of an XML identifier. - *
- * - *
Postconditions:
- *
  • The next character read from the reader won't be an identifier - * character. - *
- */ - protected void scanIdentifier(StringBuffer result) - throws IOException - { - for (;;) { - char ch = this.readChar(); - if (((ch < 'A') || (ch > 'Z')) && ((ch < 'a') || (ch > 'z')) - && ((ch < '0') || (ch > '9')) && (ch != '_') && (ch != '.') - && (ch != ':') && (ch != '-') && (ch <= '\u007E')) { - this.unreadChar(ch); - return; - } - result.append(ch); - } - } - - - /** - * This method scans an identifier from the current reader. - * - * @return the next character following the whitespace. - */ - protected char scanWhitespace() - throws IOException - { - for (;;) { - char ch = this.readChar(); - switch (ch) { - case ' ': - case '\t': - case '\n': - case '\r': - break; - default: - return ch; - } - } - } - - - /** - * This method scans an identifier from the current reader. - * The scanned whitespace is appended to result. - * - * @return the next character following the whitespace. - * - *
Preconditions:
- *
  • result != null - *
- */ - protected char scanWhitespace(StringBuffer result) - throws IOException - { - for (;;) { - char ch = this.readChar(); - switch (ch) { - case ' ': - case '\t': - case '\n': - result.append(ch); - break; - case '\r': - break; - default: - return ch; - } - } - } - - - /** - * This method scans a delimited string from the current reader. - * The scanned string without delimiters is appended to - * string. - * - *
Preconditions:
- *
  • string != null - *
  • the next char read is the string delimiter - *
- */ - protected void scanString(StringBuffer string) - throws IOException - { - /* - char delimiter = this.readChar(); - if ((delimiter != '\'') && (delimiter != '"')) { - throw this.expectedInput("' or \""); - } - for (;;) { - char ch = this.readChar(); - if (ch == delimiter) { - return; - } else if (ch == '&') { - //this.resolveEntity(string); - System.out.println("resolving ent " + delimiter); - this.resolveEntity(string, delimiter); - } else { - string.append(ch); - } - } - */ - // be slightly less strict, and don't require delimiters - char delimiter = this.readChar(); - if ((delimiter != '\'') && (delimiter != '"')) { - string.append(delimiter); - //throw this.expectedInput("' or \""); - // read until whitespace, a slash, or a closing bracket > - // don't allow delimiters in this case - for (;;) { - char ch = this.readChar(); - if ((ch == ' ') || - (ch == '\t') || (ch == '\n') || (ch == '\r') || - (ch == '/') || (ch == '>')) { - unreadChar(ch); - return; - } else { - string.append(ch); - } - } - - } else { - for (;;) { - char ch = this.readChar(); - if (ch == delimiter) { - return; - } else if (ch == '&') { - //this.resolveEntity(string); - //System.out.println("resolving ent " + delimiter); - this.resolveEntity(string, delimiter); - } else { - string.append(ch); - } - } - } - } - - - /** - * Scans a #PCDATA element. CDATA sections and entities are resolved. - * The next < char is skipped. - * The scanned data is appended to data. - * - *
Preconditions:
- *
  • data != null - *
- */ - protected void scanPCData(StringBuffer data) - throws IOException - { - for (;;) { - char ch = this.readChar(); - if (ch == '<') { - ch = this.readChar(); - if (ch == '!') { - this.checkCDATA(data); - } else { - this.unreadChar(ch); - return; - } - } else if (ch == '&') { - this.resolveEntity(data); - } else { - data.append(ch); - } - } - } - - - /** - * Scans a special tag and if the tag is a CDATA section, append its - * content to buf. - * - *
Preconditions:
- *
  • buf != null - *
  • The first < has already been read. - *
- */ - protected boolean checkCDATA(StringBuffer buf) - throws IOException - { - char ch = this.readChar(); - if (ch != '[') { - this.unreadChar(ch); - this.skipSpecialTag(0); + return this.equalsXMLElement((XMLElement) rawElement); + } catch (ClassCastException e) { return false; - } else if (! this.checkLiteral("CDATA[")) { - this.skipSpecialTag(1); // one [ has already been read + } + } + + + /** + * Returns true if the element equals another element. + * + * @param rawElement the element to compare to + */ + public boolean equalsXMLElement(XMLElement elt) { + if (! this.name.equals(elt.getName())) { return false; - } else { - int delimiterCharsSkipped = 0; - while (delimiterCharsSkipped < 3) { - ch = this.readChar(); - switch (ch) { - case ']': - if (delimiterCharsSkipped < 2) { - delimiterCharsSkipped += 1; - } else { - buf.append(']'); - buf.append(']'); - delimiterCharsSkipped = 0; - } - break; - case '>': - if (delimiterCharsSkipped < 2) { - for (int i = 0; i < delimiterCharsSkipped; i++) { - buf.append(']'); - } - delimiterCharsSkipped = 0; - buf.append('>'); - } else { - delimiterCharsSkipped = 3; - } - break; - default: - for (int i = 0; i < delimiterCharsSkipped; i += 1) { - buf.append(']'); - } - buf.append(ch); - delimiterCharsSkipped = 0; - } - } - return true; } - } - - - /** - * Skips a comment. - * - *
Preconditions:
- *
  • The first <!-- has already been read. - *
- */ - protected void skipComment() - throws IOException - { - int dashesToRead = 2; - while (dashesToRead > 0) { - char ch = this.readChar(); - if (ch == '-') { - dashesToRead -= 1; - } else { - dashesToRead = 2; + if (this.attributes.size() != elt.getAttributeCount()) { + return false; + } + Enumeration en = this.attributes.elements(); + while (en.hasMoreElements()) { + XMLAttribute attr = (XMLAttribute) en.nextElement(); + if (! elt.hasAttribute(attr.getName(), attr.getNamespace())) { + return false; + } + String value = elt.getAttribute(attr.getName(), + attr.getNamespace(), + null); + if (! attr.getValue().equals(value)) { + return false; + } + String type = elt.getAttributeType(attr.getName(), + attr.getNamespace()); + if (! attr.getType().equals(type)) { + return false; } } - if (this.readChar() != '>') { - throw this.expectedInput(">"); + if (this.children.size() != elt.getChildCount()) { + return false; } - } + for (int i = 0; i < this.children.size(); i++) { + XMLElement child1 = this.getChildAtIndex(i); + XMLElement child2 = elt.getChildAtIndex(i); - - /** - * Skips a special tag or comment. - * - * @param bracketLevel The number of open square brackets ([) that have - * already been read. - * - *
Preconditions:
- *
  • The first <! has already been read. - *
  • bracketLevel >= 0 - *
- */ - protected void skipSpecialTag(int bracketLevel) - throws IOException - { - int tagLevel = 1; // < - char stringDelimiter = '\0'; - if (bracketLevel == 0) { - char ch = this.readChar(); - if (ch == '[') { - bracketLevel += 1; - } else if (ch == '-') { - ch = this.readChar(); - if (ch == '[') { - bracketLevel += 1; - } else if (ch == ']') { - bracketLevel -= 1; - } else if (ch == '-') { - this.skipComment(); - return; - } - } - } - while (tagLevel > 0) { - char ch = this.readChar(); - if (stringDelimiter == '\0') { - if ((ch == '"') || (ch == '\'')) { - stringDelimiter = ch; - } else if (bracketLevel <= 0) { - if (ch == '<') { - tagLevel += 1; - } else if (ch == '>') { - tagLevel -= 1; - } - } - if (ch == '[') { - bracketLevel += 1; - } else if (ch == ']') { - bracketLevel -= 1; - } - } else { - if (ch == stringDelimiter) { - stringDelimiter = '\0'; - } - } - } - } - - - /** - * Scans the data for literal text. - * Scanning stops when a character does not match or after the complete - * text has been checked, whichever comes first. - * - * @param literal the literal to check. - * - *
Preconditions:
- *
  • literal != null - *
- */ - protected boolean checkLiteral(String literal) - throws IOException - { - int length = literal.length(); - for (int i = 0; i < length; i += 1) { - if (this.readChar() != literal.charAt(i)) { + if (! child1.equalsXMLElement(child2)) { return false; } } return true; } - - /** - * Reads a character from a reader. - */ - protected char readChar() - throws IOException - { - if (this.charReadTooMuch != '\0') { - char ch = this.charReadTooMuch; - this.charReadTooMuch = '\0'; - return ch; - } else { - int i = this.reader.read(); - if (i < 0) { - throw this.unexpectedEndOfData(); - } else if (i == 10) { - this.parserLineNumber += 1; - return '\n'; - } else { - if (DEBUG) System.out.println((char) i); - return (char) i; - } - } - } - - - /** - * Scans an XML element. - * - * @param elt The element that will contain the result. - * - *
Preconditions:
- *
  • The first < has already been read. - *
  • elt != null - *
- */ - protected void scanElement(XMLElement elt) - throws IOException - { - StringBuffer buf = new StringBuffer(); - this.scanIdentifier(buf); - String bname = buf.toString(); - elt.setName(bname); - if (DEBUG) System.out.println("got bname '" + bname + "'" + " " + parserLineNumber); - char ch = this.scanWhitespace(); - while ((ch != '>') && (ch != '/')) { - if (DEBUG) System.out.println("up here then"); - buf.setLength(0); - this.unreadChar(ch); - this.scanIdentifier(buf); - String key = buf.toString(); - if (DEBUG) System.out.println(" attr is " + key); - ch = this.scanWhitespace(); - /* - if (ch != '=') { - throw this.expectedInput("="); - } - */ - if (ch != '=') { - if (ignoreMissingAttributes) { - if (DEBUG) System.out.println("expected = got " + ch); - this.unreadChar(ch); - //buf.setLength(0); - elt.setAttribute(key, ""); - if (DEBUG) System.out.println(" value is empty"); - } else { - throw this.expectedInput("="); - } - } else { - this.unreadChar(this.scanWhitespace()); - buf.setLength(0); - this.scanString(buf); - elt.setAttribute(key, buf); - if (DEBUG) System.out.println(" value is " + buf); - } - ch = this.scanWhitespace(); - if (DEBUG) System.out.println("scan of white produced " + ch); - } - if (DEBUG) System.out.println("out here now?"); - if (ch == '/') { - ch = this.readChar(); - if (ch != '>') { - throw this.expectedInput(">"); - } - if (DEBUG) System.out.println("leaving"); - return; - } - buf.setLength(0); - ch = this.scanWhitespace(buf); - if (DEBUG) System.out.println("not yet leaving " + ch); - if (ch != '<') { - this.unreadChar(ch); - this.scanPCData(buf); - } else { - for (;;) { - ch = this.readChar(); - if (ch == '!') { - if (this.checkCDATA(buf)) { - this.scanPCData(buf); - break; - } else { - ch = this.scanWhitespace(buf); - if (ch != '<') { - this.unreadChar(ch); - this.scanPCData(buf); - break; - } - } - } else { - if ((ch != '/') || this.ignoreWhitespace) { - buf.setLength(0); - } - if (ch == '/') { - this.unreadChar(ch); - } - break; - } - } - } - if (buf.length() == 0) { - while (ch != '/') { - if (ch == '!') { - ch = this.readChar(); - if (ch != '-') { - throw this.expectedInput("Comment or Element"); - } - ch = this.readChar(); - if (ch != '-') { - throw this.expectedInput("Comment or Element"); - } - this.skipComment(); - } else { - this.unreadChar(ch); - XMLElement child = this.createAnotherElement(); - this.scanElement(child); - elt.addChild(child); - } - ch = this.scanWhitespace(); - if (ch != '<') { - throw this.expectedInput("<"); - } - ch = this.readChar(); - } - this.unreadChar(ch); - } else { - if (this.ignoreWhitespace) { - elt.setContent(buf.toString().trim()); - } else { - elt.setContent(buf.toString()); - } - } - ch = this.readChar(); - if (ch != '/') { - throw this.expectedInput("/"); - } - this.unreadChar(this.scanWhitespace()); - if (! this.checkLiteral(bname)) { - throw this.expectedInput(bname); - } - if (this.scanWhitespace() != '>') { - throw this.expectedInput(">"); - } - } - - - /** - * Resolves an entity. The name of the entity is read from the reader. - * The value of the entity is appended to buf. - * - * @param buf Where to put the entity value. - * - *
Preconditions:
- *
  • The first & has already been read. - *
  • buf != null - *
- */ - protected void resolveEntity(StringBuffer buf) - throws IOException - { - char ch = '\0'; - StringBuffer keyBuf = new StringBuffer(); - for (;;) { - ch = this.readChar(); - if (ch == ';') { - break; - } - keyBuf.append(ch); - } - //System.out.println("found ent " + keyBuf); - String key = keyBuf.toString(); - if (key.charAt(0) == '#') { - try { - if (key.charAt(1) == 'x') { - ch = (char) Integer.parseInt(key.substring(2), 16); - } else { - ch = (char) Integer.parseInt(key.substring(1), 10); - } - } catch (NumberFormatException e) { - throw this.unknownEntity(key); - } - buf.append(ch); - } else { - char[] value = (char[]) this.entities.get(key); - // [fry] modified this to provide the option of ignoring missing entities - if (value == null) { - if (!ignoreUnknownEntities) { - throw this.unknownEntity(key); - } else { - // push the unknown entity back onto the stream - buf.append("&" + key + ";"); - } - } else { - if (DEBUG) System.out.println("appending entity " + new String(value)); - buf.append(value); - } - } - } - - - /** - * Version of resolveEntity that gives up when it hits its ending delimiter. - * Handles parsing stuff where the URL contains an ampersand. - */ - protected void resolveEntity(StringBuffer buf, char delimiter) throws IOException - { - char ch = '\0'; - StringBuffer keyBuf = new StringBuffer(); - for (;;) { - ch = this.readChar(); - if (ch == ';') { - break; - } - // this wasn't an entity, and we've reached the end of the string - if (ch == delimiter) { - if (DEBUG) System.out.println("found end of delim " + keyBuf); - buf.append(keyBuf); - unreadChar(ch); - return; - } - keyBuf.append(ch); - } - String key = keyBuf.toString(); - if (key.charAt(0) == '#') { - try { - if (key.charAt(1) == 'x') { - ch = (char) Integer.parseInt(key.substring(2), 16); - } else { - ch = (char) Integer.parseInt(key.substring(1), 10); - } - } catch (NumberFormatException e) { - throw this.unknownEntity(key); - } - buf.append(ch); - } else { - char[] value = (char[]) this.entities.get(key); - if (value == null) { - if (!ignoreUnknownEntities) { - throw this.unknownEntity(key); - } else { - // could also just push back the text instead of a blank? - } - } else { - if (DEBUG) System.out.println("appending entity2 " + new String(value)); - buf.append(value); - } - } - } - - /** - * Pushes a character back to the read-back buffer. - * - * @param ch The character to push back. - * - *
Preconditions:
- *
  • The read-back buffer is empty. - *
  • ch != '\0' - *
- */ - protected void unreadChar(char ch) - { - this.charReadTooMuch = ch; - } - - - /** - * Creates a parse exception for when an invalid valueset is given to - * a method. - * - * @param name The name of the entity. - * - *
Preconditions:
- *
  • name != null - *
- */ - protected XMLParseException invalidValueSet(String name) - { - String msg = "Invalid value set (entity name = \"" + name + "\")"; - return new XMLParseException(this.getName(), this.parserLineNumber, msg); - } - - - /** - * Creates a parse exception for when an invalid value is given to a - * method. - * - * @param name The name of the entity. - * @param value The value of the entity. - * - *
Preconditions:
- *
  • name != null - *
  • value != null - *
- */ - protected XMLParseException invalidValue(String name, - String value) - { - String msg = "Attribute \"" + name + "\" does not contain a valid " - + "value (\"" + value + "\")"; - return new XMLParseException(this.getName(), this.parserLineNumber, msg); - } - - - /** - * Creates a parse exception for when the end of the data input has been - * reached. - */ - protected XMLParseException unexpectedEndOfData() - { - String msg = "Unexpected end of data reached"; - return new XMLParseException(this.getName(), this.parserLineNumber, msg); - } - - - /** - * Creates a parse exception for when a syntax error occured. - * - * @param context The context in which the error occured. - * - *
Preconditions:
- *
  • context != null - *
  • context.length() > 0 - *
- */ - protected XMLParseException syntaxError(String context) - { - String msg = "Syntax error while parsing " + context; - return new XMLParseException(this.getName(), this.parserLineNumber, msg); - } - - - /** - * Creates a parse exception for when the next character read is not - * the character that was expected. - * - * @param charSet The set of characters (in human readable form) that was - * expected. - * - *
Preconditions:
- *
  • charSet != null - *
  • charSet.length() > 0 - *
- */ - protected XMLParseException expectedInput(String charSet) - { - String msg = "Expected: " + charSet; - return new XMLParseException(this.getName(), this.parserLineNumber, msg); - } - - - /** - * Creates a parse exception for when an entity could not be resolved. - * - * @param name The name of the entity. - * - *
Preconditions:
- *
  • name != null - *
  • name.length() > 0 - *
- */ - protected XMLParseException unknownEntity(String name) - { - String msg = "Unknown or invalid entity: &" + name + ";"; - return new XMLParseException(this.getName(), this.parserLineNumber, msg); + + public String toString() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStreamWriter osw = new OutputStreamWriter(baos); + XMLWriter writer = new XMLWriter(osw); + try { + writer.write(this, true, 2, true); + } catch (IOException e) { + e.printStackTrace(); + } + return baos.toString(); } } diff --git a/xml/src/processing/xml/XMLEntityResolver.java b/xml/src/processing/xml/XMLEntityResolver.java new file mode 100644 index 000000000..7388c8708 --- /dev/null +++ b/xml/src/processing/xml/XMLEntityResolver.java @@ -0,0 +1,173 @@ +/* XMLEntityResolver.java NanoXML/Java + * + * $Revision: 1.4 $ + * $Date: 2002/01/04 21:03:29 $ + * $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.util.Hashtable; +import java.io.Reader; +import java.io.StringReader; + + +/** + * An XMLEntityResolver resolves entities. + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.4 $ + */ +public class XMLEntityResolver +{ + + /** + * The entities. + */ + private Hashtable entities; + + + /** + * Initializes the resolver. + */ + public XMLEntityResolver() + { + this.entities = new Hashtable(); + this.entities.put("amp", "&"); + this.entities.put("quot", """); + this.entities.put("apos", "'"); + this.entities.put("lt", "<"); + this.entities.put("gt", ">"); + } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() + throws Throwable + { + this.entities.clear(); + this.entities = null; + super.finalize(); + } + + + /** + * Adds an internal entity. + * + * @param name the name of the entity. + * @param value the value of the entity. + */ + public void addInternalEntity(String name, + String value) + { + if (! this.entities.containsKey(name)) { + this.entities.put(name, value); + } + } + + + /** + * Adds an external entity. + * + * @param name the name of the entity. + * @param publicID the public ID of the entity, which may be null. + * @param systemID the system ID of the entity. + */ + public void addExternalEntity(String name, + String publicID, + String systemID) + { + if (! this.entities.containsKey(name)) { + this.entities.put(name, new String[] { publicID, systemID } ); + } + } + + + /** + * Returns a Java reader containing the value of an entity. + * + * @param xmlReader the current XML reader + * @param name the name of the entity. + * + * @return the reader, or null if the entity could not be resolved. + */ + public Reader getEntity(StdXMLReader xmlReader, + String name) + throws XMLParseException + { + Object obj = this.entities.get(name); + + if (obj == null) { + return null; + } else if (obj instanceof java.lang.String) { + return new StringReader((String)obj); + } else { + String[] id = (String[]) obj; + return this.openExternalEntity(xmlReader, id[0], id[1]); + } + } + + + /** + * Returns true if an entity is external. + * + * @param name the name of the entity. + */ + public boolean isExternalEntity(String name) + { + Object obj = this.entities.get(name); + return ! (obj instanceof java.lang.String); + } + + + /** + * Opens an external entity. + * + * @param xmlReader the current XML reader + * @param publicID the public ID, which may be null + * @param systemID the system ID + * + * @return the reader, or null if the reader could not be created/opened + */ + protected Reader openExternalEntity(StdXMLReader xmlReader, + String publicID, + String systemID) + throws XMLParseException + { + String parentSystemID = xmlReader.getSystemID(); + + try { + return xmlReader.openStream(publicID, systemID); + } catch (Exception e) { + throw new XMLParseException(parentSystemID, + xmlReader.getLineNr(), + "Could not open external entity " + + "at system ID: " + systemID); + } + } + +} diff --git a/xml/src/processing/xml/XMLException.java b/xml/src/processing/xml/XMLException.java new file mode 100644 index 000000000..8515d481a --- /dev/null +++ b/xml/src/processing/xml/XMLException.java @@ -0,0 +1,286 @@ +/* XMLException.java NanoXML/Java + * + * $Revision: 1.4 $ + * $Date: 2002/01/04 21:03:29 $ + * $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.PrintStream; +import java.io.PrintWriter; + + +/** + * An XMLException is thrown when an exception occurred while processing the + * XML data. + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.4 $ + */ +public class XMLException + extends Exception +{ + + /** + * The message of the exception. + */ + private String msg; + + + /** + * The system ID of the XML data where the exception occurred. + */ + private String systemID; + + + /** + * The line number in the XML data where the exception occurred. + */ + private int lineNr; + + + /** + * Encapsulated exception. + */ + private Exception encapsulatedException; + + + /** + * Creates a new exception. + * + * @param msg the message of the exception. + */ + public XMLException(String msg) + { + this(null, -1, null, msg, false); + } + + + /** + * Creates a new exception. + * + * @param e the encapsulated exception. + */ + public XMLException(Exception e) + { + this(null, -1, e, "Nested Exception", false); + } + + + /** + * Creates a new exception. + * + * @param systemID the system ID of the XML data where the exception + * occurred + * @param lineNr the line number in the XML data where the exception + * occurred. + * @param e the encapsulated exception. + */ + public XMLException(String systemID, + int lineNr, + Exception e) + { + this(systemID, lineNr, e, "Nested Exception", true); + } + + + /** + * Creates a new exception. + * + * @param systemID the system ID of the XML data where the exception + * occurred + * @param lineNr the line number in the XML data where the exception + * occurred. + * @param msg the message of the exception. + */ + public XMLException(String systemID, + int lineNr, + String msg) + { + this(systemID, lineNr, null, msg, true); + } + + + /** + * Creates a new exception. + * + * @param systemID the system ID from where the data came + * @param lineNr the line number in the XML data where the exception + * occurred. + * @param e the encapsulated exception. + * @param msg the message of the exception. + * @param reportParams true if the systemID, lineNr and e params need to be + * appended to the message + */ + public XMLException(String systemID, + int lineNr, + Exception e, + String msg, + boolean reportParams) + { + super(XMLException.buildMessage(systemID, lineNr, e, msg, + reportParams)); + this.systemID = systemID; + this.lineNr = lineNr; + this.encapsulatedException = e; + this.msg = XMLException.buildMessage(systemID, lineNr, e, msg, + reportParams); + } + + + /** + * Builds the exception message + * + * @param systemID the system ID from where the data came + * @param lineNr the line number in the XML data where the exception + * occurred. + * @param e the encapsulated exception. + * @param msg the message of the exception. + * @param reportParams true if the systemID, lineNr and e params need to be + * appended to the message + */ + private static String buildMessage(String systemID, + int lineNr, + Exception e, + String msg, + boolean reportParams) + { + String str = msg; + + if (reportParams) { + if (systemID != null) { + str += ", SystemID='" + systemID + "'"; + } + + if (lineNr >= 0) { + str += ", Line=" + lineNr; + } + + if (e != null) { + str += ", Exception: " + e; + } + } + + return str; + } + + + /** + * Cleans up the object when it's destroyed. + */ + protected void finalize() + throws Throwable + { + this.systemID = null; + this.encapsulatedException = null; + super.finalize(); + } + + + /** + * Returns the system ID of the XML data where the exception occurred. + * If there is no system ID known, null is returned. + */ + public String getSystemID() + { + return this.systemID; + } + + + /** + * Returns the line number in the XML data where the exception occurred. + * If there is no line number known, -1 is returned. + */ + public int getLineNr() + { + return this.lineNr; + } + + + /** + * Returns the encapsulated exception, or null if no exception is + * encapsulated. + */ + public Exception getException() + { + return this.encapsulatedException; + } + + + /** + * Dumps the exception stack to a print writer. + * + * @param writer the print writer + */ + public void printStackTrace(PrintWriter writer) + { + super.printStackTrace(writer); + + if (this.encapsulatedException != null) { + writer.println("*** Nested Exception:"); + this.encapsulatedException.printStackTrace(writer); + } + } + + + /** + * Dumps the exception stack to an output stream. + * + * @param stream the output stream + */ + public void printStackTrace(PrintStream stream) + { + super.printStackTrace(stream); + + if (this.encapsulatedException != null) { + stream.println("*** Nested Exception:"); + this.encapsulatedException.printStackTrace(stream); + } + } + + + /** + * Dumps the exception stack to System.err. + */ + public void printStackTrace() + { + super.printStackTrace(); + + if (this.encapsulatedException != null) { + System.err.println("*** Nested Exception:"); + this.encapsulatedException.printStackTrace(); + } + } + + + /** + * Returns a string representation of the exception. + */ + public String toString() + { + return this.msg; + } + +} diff --git a/xml/src/processing/xml/XMLParseException.java b/xml/src/processing/xml/XMLParseException.java index 30f0c4f85..aa7915d94 100644 --- a/xml/src/processing/xml/XMLParseException.java +++ b/xml/src/processing/xml/XMLParseException.java @@ -1,125 +1,69 @@ -/* - This code is from NanoXML 2.2.3 Lite - Copyright (C) 2000-2002 Marc De Scheemaecker, All Rights Reserved. - - Additional modifications Copyright (c) 2006 Ben Fry and Casey Reas - to make the code better-suited for use with Processing. - - Original license notice from Marc De Scheemaecker: - - 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. -*/ +/* XMLParseException.java NanoXML/Java + * + * $Revision: 1.3 $ + * $Date: 2002/01/04 21:03:29 $ + * $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; /** - * An XMLParseException is thrown when an error occures while parsing an XML - * string. - * - * @see processing.xml.XMLElement + * An XMLParseException is thrown when the XML passed to the XML parser is not + * well-formed. * * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.3 $ */ public class XMLParseException - extends RuntimeException + extends XMLException { - /** - * Indicates that no line number has been associated with this exception. - */ - public static final int NO_LINE = -1; + /** + * Creates a new exception. + * + * @param msg the message of the exception. + */ + public XMLParseException(String msg) + { + super(msg); + } - /** - * The line number in the source code where the error occurred, or - * NO_LINE if the line number is unknown. - * - *
Invariants:
- *
  • lineNumber > 0 || lineNumber == NO_LINE - *
- */ - private int lineNumber; - - - /** - * Creates an exception. - * - * @param name The name of the element where the error is located. - * @param message A message describing what went wrong. - * - *
Preconditions:
- *
  • message != null - *
- * - *
Postconditions:
- *
  • getLineNumber() => NO_LINE - *
- */ - public XMLParseException(String name, - String message) - { - super("XML Parse Exception during parsing of " - + ((name == null) ? "the XML definition" - : ("a " + name + " element")) - + ": " + message); - this.lineNumber = XMLParseException.NO_LINE; - } - - - /** - * Creates an exception. - * - * @param name The name of the element where the error is located. - * @param lineNumber The number of the line in the input. - * @param message A message describing what went wrong. - * - *
Preconditions:
- *
  • message != null - *
  • lineNumber > 0 - *
- * - *
Postconditions:
- *
  • getLineNumber() => lineNumber - *
- */ - public XMLParseException(String name, - int lineNumber, - String message) - { - super("XML Parse Exception during parsing of " - + ((name == null) ? "the XML definition" - : ("a " + name + " element")) - + " at line " + lineNumber + ": " + message); - this.lineNumber = lineNumber; - } - - - /** - * Where the error occurred, or NO_LINE if the line number is - * unknown. - * - * @see processing.xml.XMLParseException#NO_LINE - */ - public int getLineNumber() - { - return this.lineNumber; - } + /** + * Creates a new exception. + * + * @param systemID the system ID from where the data came + * @param lineNr the line number in the XML data where the exception + * occurred. + * @param msg the message of the exception. + */ + public XMLParseException(String systemID, + int lineNr, + String msg) + { + super(systemID, lineNr, null, msg, true); + } } diff --git a/xml/src/processing/xml/XMLUtil.java b/xml/src/processing/xml/XMLUtil.java new file mode 100644 index 000000000..261019b13 --- /dev/null +++ b/xml/src/processing/xml/XMLUtil.java @@ -0,0 +1,756 @@ +/* XMLUtil.java NanoXML/Java + * + * $Revision: 1.5 $ + * $Date: 2002/02/03 21:19:38 $ + * $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.IOException; +import java.io.Reader; + + +/** + * Utility methods for NanoXML. + * + * @author Marc De Scheemaecker + * @version $Name: RELEASE_2_2_1 $, $Revision: 1.5 $ + */ +class XMLUtil +{ + + /** + * Skips the remainder of a comment. + * It is assumed that <!- is already read. + * + * @param reader the reader + * + * @throws java.io.IOException + * if an error occurred reading the data + */ + static void skipComment(StdXMLReader reader) + throws IOException, + XMLParseException + { + if (reader.read() != '-') { + XMLUtil.errorExpectedInput(reader.getSystemID(), + reader.getLineNr(), + "