/* * 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 org.freeplane.n3.nanoxml; 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 { /** * Returns true if the data starts with <I>literal</I>. Enough chars are * read to determine this result. * * @param reader * the reader * @param literal * the literal to check * @throws java.io.IOException * if an error occurred reading the data */ static boolean checkLiteral(final IXMLReader reader, final String literal) throws IOException, XMLParseException { for (int i = 0; i < literal.length(); i++) { if (reader.read() != literal.charAt(i)) { return false; } } return true; } /** * Throws an XMLParseException to indicate that extra data is encountered in * a closing tag. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source */ static void errorClosingTagNotEmpty(final String systemID, final int lineNr) throws XMLParseException { throw new XMLParseException(systemID, lineNr, "Closing tag must be empty"); } /** * Throws an XMLParseException to indicate that an expected string is not * encountered. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param expectedString * the string that is expected */ static void errorExpectedInput(final String systemID, final int lineNr, final String expectedString) throws XMLParseException { throw new XMLParseException(systemID, lineNr, "Expected: " + expectedString); } /** * Throws an XMLValidationException to indicate that an attribute has an * invalid value. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param elementName * the name of the element * @param attributeName * the name of the attribute * @param attributeValue * the value of that attribute */ static void errorInvalidAttributeValue(final String systemID, final int lineNr, final String elementName, final String attributeName, final String attributeValue) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.ATTRIBUTE_WITH_INVALID_VALUE, systemID, lineNr, elementName, attributeName, attributeValue, "Invalid value for attribute " + attributeName); } /** * Throws an XMLParseException to indicate that an entity could not be * resolved. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param entity * the name of the entity */ static void errorInvalidEntity(final String systemID, final int lineNr, final String entity) throws XMLParseException { throw new XMLParseException(systemID, lineNr, "Invalid entity: `&" + entity + ";'"); } /** * Throws an XMLParseException to indicate that a string is not expected at * this point. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param unexpectedString * the string that is unexpected */ static void errorInvalidInput(final String systemID, final int lineNr, final String unexpectedString) throws XMLParseException { throw new XMLParseException(systemID, lineNr, "Invalid input: " + unexpectedString); } /** * Throws an XMLValidationException to indicate that an attribute is * missing. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param elementName * the name of the element * @param attributeName * the name of the missing attribute */ static void errorMissingAttribute(final String systemID, final int lineNr, final String elementName, final String attributeName) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.MISSING_ATTRIBUTE, systemID, lineNr, elementName, attributeName, /* attributeValue */null, "Element " + elementName + " expects an attribute named " + attributeName); } /** * Throws an XMLValidationException to indicate that an element is missing. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param parentElementName * the name of the parent element * @param missingElementName * the name of the missing element */ static void errorMissingElement(final String systemID, final int lineNr, final String parentElementName, final String missingElementName) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.MISSING_ELEMENT, systemID, lineNr, missingElementName, /* attributeName */null, /* attributeValue */null, "Element " + parentElementName + " expects to have a " + missingElementName); } /** * Throws an XMLValidationException to indicate that a #PCDATA element was * missing. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param parentElementName * the name of the parent element */ static void errorMissingPCData(final String systemID, final int lineNr, final String parentElementName) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.MISSING_PCDATA, systemID, lineNr, /* elementName */null, /* attributeName */null, /* attributeValue */null, "Missing #PCDATA in element " + parentElementName); } /** * Throws an XMLValidationException to indicate that an attribute is * unexpected. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param elementName * the name of the element * @param attributeName * the name of the unexpected attribute */ static void errorUnexpectedAttribute(final String systemID, final int lineNr, final String elementName, final String attributeName) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.UNEXPECTED_ATTRIBUTE, systemID, lineNr, elementName, attributeName, /* attributeValue */null, "Element " + elementName + " did not expect an attribute " + "named " + attributeName); } /** * Throws an XMLParseException to indicate that a CDATA section is * unexpected at this point. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source */ static void errorUnexpectedCDATA(final String systemID, final int lineNr) throws XMLParseException { throw new XMLParseException(systemID, lineNr, "No CDATA section is expected here"); } /** * Throws an XMLValidationException to indicate that an element is * unexpected. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param parentElementName * the name of the parent element * @param unexpectedElementName * the name of the unexpected element */ static void errorUnexpectedElement(final String systemID, final int lineNr, final String parentElementName, final String unexpectedElementName) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.UNEXPECTED_ELEMENT, systemID, lineNr, unexpectedElementName, /* attributeName */null, /* attributeValue */null, "Unexpected " + unexpectedElementName + " in a " + parentElementName); } /** * Throws an XMLParseException to indicate that an entity reference is * unexpected at this point. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param entity * the name of the entity */ static void errorUnexpectedEntity(final String systemID, final int lineNr, final String entity) throws XMLParseException { throw new XMLParseException(systemID, lineNr, "No entity reference is expected here (" + entity + ")"); } /** * Throws an XMLValidationException to indicate that a #PCDATA element was * unexpected. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param parentElementName * the name of the parent element */ static void errorUnexpectedPCData(final String systemID, final int lineNr, final String parentElementName) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.UNEXPECTED_PCDATA, systemID, lineNr, /* elementName */null, /* attributeName */null, /* attributeValue */null, "Unexpected #PCDATA in element " + parentElementName); } /** * Throws an XMLParseException to indicate that the closing tag of an * element does not match the opening tag. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param expectedName * the name of the opening tag * @param wrongName * the name of the closing tag */ static void errorWrongClosingTag(final String systemID, final int lineNr, final String expectedName, final String wrongName) throws XMLParseException { throw new XMLParseException(systemID, lineNr, "Closing tag does not match opening tag: `" + wrongName + "' != `" + expectedName + "'"); } /** * Processes a character literal. * * @param entity * the entity * @throws java.io.IOException * if an error occurred reading the data */ static char processCharLiteral(String entity) throws IOException, XMLParseException { if (entity.charAt(2) == 'x') { entity = entity.substring(3, entity.length() - 1); return (char) Integer.parseInt(entity, 16); } else { entity = entity.substring(2, entity.length() - 1); return (char) Integer.parseInt(entity, 10); } } /** * Processes an entity. * * @param entity * the entity * @param reader * the reader * @param entityResolver * the entity resolver * @throws java.io.IOException * if an error occurred reading the data */ static void processEntity(String entity, final IXMLReader reader, final IXMLEntityResolver entityResolver) throws IOException, XMLParseException { entity = entity.substring(1, entity.length() - 1); final Reader entityReader = entityResolver.getEntity(reader, entity); if (entityReader == null) { XMLUtil.errorInvalidEntity(reader.getSystemID(), reader.getLineNr(), entity); } final boolean externalEntity = entityResolver.isExternalEntity(entity); reader.startNewStream(entityReader, !externalEntity); } /** * Reads a character from the reader. * * @param reader * the reader * @param entityChar * the escape character (& or %) used to indicate an entity * @return the character, or an entity expression (like e.g. &lt;) * @throws java.io.IOException * if an error occurred reading the data */ static String read(final IXMLReader reader, final char entityChar) throws IOException, XMLParseException { char ch = reader.read(); final StringBuilder buf = new StringBuilder(); buf.append(ch); if (ch == entityChar) { while (ch != ';') { ch = reader.read(); buf.append(ch); } } return buf.toString(); } /** * Reads a character from the reader disallowing entities. * * @param reader * the reader * @param entityChar * the escape character (& or %) used to indicate an entity */ static char readChar(final IXMLReader reader, final char entityChar) throws IOException, XMLParseException { final String str = XMLUtil.read(reader, entityChar); final char ch = str.charAt(0); if (ch == entityChar) { XMLUtil.errorUnexpectedEntity(reader.getSystemID(), reader.getLineNr(), str); } return ch; } /** * Retrieves an identifier from the data. * * @param reader * the reader * @throws java.io.IOException * if an error occurred reading the data */ static String scanIdentifier(final IXMLReader reader) throws IOException, XMLParseException { final StringBuilder result = new StringBuilder(); for (;;) { final char ch = reader.read(); if ((ch == '_') || (ch == ':') || (ch == '-') || (ch == '.') || ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || ((ch >= '0') && (ch <= '9')) || (ch > '\u007E')) { result.append(ch); } else { reader.unread(ch); break; } } return result.toString(); } /** * Scans a public ID. * * @param publicID * will contain the public ID * @param reader * the reader * @return the system ID * @throws java.io.IOException * if an error occurred reading the data */ static String scanPublicID(final StringBuilder publicID, final IXMLReader reader) throws IOException, XMLParseException { if (!XMLUtil.checkLiteral(reader, "UBLIC")) { return null; } XMLUtil.skipWhitespace(reader, null); publicID.append(XMLUtil.scanString(reader, '\0', null)); XMLUtil.skipWhitespace(reader, null); return XMLUtil.scanString(reader, '\0', null); } /** * Retrieves a delimited string from the data. * * @param reader * the reader * @param entityChar * the escape character (& or %) * @param entityResolver * the entity resolver * @throws java.io.IOException * if an error occurred reading the data */ static String scanString(final IXMLReader reader, final char entityChar, final IXMLEntityResolver entityResolver) throws IOException, XMLParseException { final StringBuilder result = new StringBuilder(); final int startingLevel = reader.getStreamLevel(); final char delim = reader.read(); if ((delim != '\'') && (delim != '"')) { XMLUtil.errorExpectedInput(reader.getSystemID(), reader.getLineNr(), "delimited string"); } for (;;) { String str = XMLUtil.read(reader, entityChar); final char ch = str.charAt(0); if (ch == entityChar) { if (str.charAt(1) == '#') { result.append(XMLUtil.processCharLiteral(str)); } else { XMLUtil.processEntity(str, reader, entityResolver); } } else if (ch == '&') { reader.unread(ch); str = XMLUtil.read(reader, '&'); if (str.charAt(1) == '#') { result.append(XMLUtil.processCharLiteral(str)); } else { result.append(str); } } else if (reader.getStreamLevel() == startingLevel) { if (ch == delim) { break; } else if ((ch == 9) || (ch == 10) || (ch == 13)) { result.append(' '); } else { result.append(ch); } } else { result.append(ch); } } return result.toString(); } /** * Scans a system ID. * * @param reader * the reader * @return the system ID * @throws java.io.IOException * if an error occurred reading the data */ static String scanSystemID(final IXMLReader reader) throws IOException, XMLParseException { if (!XMLUtil.checkLiteral(reader, "YSTEM")) { return null; } XMLUtil.skipWhitespace(reader, null); return XMLUtil.scanString(reader, '\0', null); } /** * 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(final IXMLReader reader) throws IOException, XMLParseException { if (reader.read() != '-') { XMLUtil.errorExpectedInput(reader.getSystemID(), reader.getLineNr(), "<!--"); } int dashesRead = 0; for (;;) { final char ch = reader.read(); switch (ch) { case '-': dashesRead++; break; case '>': if (dashesRead == 2) { return; } default: dashesRead = 0; } } } /** * Skips the remainder of the current XML tag. * * @param reader * the reader * @throws java.io.IOException * if an error occurred reading the data */ static void skipTag(final IXMLReader reader) throws IOException, XMLParseException { int level = 1; while (level > 0) { final char ch = reader.read(); switch (ch) { case '<': ++level; break; case '>': --level; break; } } } /** * Skips whitespace from the reader. * * @param reader * the reader * @param buffer * where to put the whitespace; null if the whitespace does not * have to be stored. * @throws java.io.IOException * if an error occurred reading the data */ static void skipWhitespace(final IXMLReader reader, final StringBuilder buffer) throws IOException { char ch; if (buffer == null) { do { ch = reader.read(); } while ((ch == ' ') || (ch == '\t') || (ch == '\n')); } else { for (;;) { ch = reader.read(); if ((ch != ' ') && (ch != '\t') && (ch != '\n')) { break; } if (ch == '\n') { buffer.append('\n'); } else { buffer.append(' '); } } } reader.unread(ch); } /** * Throws an XMLValidationException. * * @param systemID * the system ID of the data source * @param lineNr * the line number in the data source * @param message * the error message * @param elementName * the name of the element * @param attributeName * the name of the attribute * @param attributeValue * the value of that attribute */ static void validationError(final String systemID, final int lineNr, final String message, final String elementName, final String attributeName, final String attributeValue) throws XMLValidationException { throw new XMLValidationException(XMLValidationException.MISC_ERROR, systemID, lineNr, elementName, attributeName, attributeValue, message); } }