/* XMLElement.java * * $Revision: 1.2 $ * $Date: 2002/08/03 04:36:34 $ * $Name: $ * * This file is part of NanoXML 2 Lite. * 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. *****************************************************************************/ /* JAM: hacked the source to remove unneeded methods and comments. */ package net.sourceforge.nanoxml; import java.io.*; import java.util.*; import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.util.logging.OutputController; /** * XMLElement is a representation of an XML object. The object is able to parse * XML code. *

*
Parsing XML Data
*
* You can parse XML data using the following code: *
*
Retrieving Attributes
*
* You can enumerate the attributes of an element using the method * {@link #enumerateAttributeNames() enumerateAttributeNames}. * The attribute values can be retrieved using the method * {@link #getAttribute(java.lang.String) getAttribute}. * The following example shows how to list the attributes of an element: *
*
Retrieving Child Elements
*
* You can enumerate the children of an element using * {@link #enumerateChildren() enumerateChildren}. * The number of child elements can be retrieved using * {@link #countChildren() countChildren}. *
*
Elements Containing Character Data
*
* If an elements contains character data, like in the following example: * * you can retrieve that data using the method * {@link #getContent() getContent}. *
*
Subclassing XMLElement
*
* When subclassing XMLElement, you need to override the method * {@link #createAnotherElement() createAnotherElement} * which has to return a new copy of the receiver. *
*

* * @see net.sourceforge.nanoxml.XMLParseException * * @author Marc De Scheemaecker * <cyberelf@mac.com> * @version $Name: $, $Revision: 1.2 $ */ public class XMLElement { /** * The attributes given to the element. * *

Invariants:
*
*/ private Hashtable attributes; /** * 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. *
*/ 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. *
*/ private String contents; /** * 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 *
*/ private Hashtable entities; /** * The line number where the element starts. * *
Invariants:
*
  • {@code lineNr >= 0} *
*/ private int lineNr; /** * 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; /** * Character read too much for the comment remover. */ private char sanitizeCharReadTooMuch; /** * 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:
*
  • parserLineNr > 0 while the parse method is running. *
*/ private int parserLineNr; /** * Creates and initializes a new XML element. * Calling the construction is equivalent to: *
  • new XMLElement(new Hashtable(), false, true) *
* *
Postconditions:
*
  • countChildren() => 0 *
  • enumerateChildren() => empty enumeration *
  • enumeratePropertyNames() => empty enumeration *
  • getChildren() => empty vector *
  • getContent() => "" *
  • getLineNr() => 0 *
  • getName() => null *
* */ public XMLElement() { this(new Hashtable(), false, true, true); } /** * 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 (client code calling this constructor). * @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() => "" *
  • getLineNr() => 0 *
  • getName() => null *
* */ protected XMLElement(Hashtable entities, boolean skipLeadingWhitespace, boolean fillBasicConversionTable, boolean ignoreCase) { this.ignoreWhitespace = skipLeadingWhitespace; this.ignoreCase = ignoreCase; this.name = null; this.contents = ""; this.attributes = new Hashtable(); this.children = new Vector(); this.entities = entities; this.lineNr = 0; Enumeration e = this.entities.keys(); while (e.hasMoreElements()) { String key = e.nextElement(); Object value = this.entities.get(key); if (value instanceof String) { entities.put(key, ((String) value).toCharArray()); } } 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[] { '>' }); } } /** * 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 *
* */ public void addChild(XMLElement child) { this.children.addElement(child); } /** * 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 *
  • value != null *
* *
Postconditions:
*
  • enumerateAttributeNames() * => old.enumerateAttributeNames() + name *
  • getAttribute(name) => value *
* */ public void setAttribute(String name, Object value) { if (this.ignoreCase) { name = name.toUpperCase(); } this.attributes.put(name, value.toString()); } /** * Returns the number of child elements of the element. * *
Postconditions:
*
  • {@code result >= 0} *
* */ public int countChildren() { return this.children.size(); } /** * Enumerates the attribute names. * *
Postconditions:
*
  • result != null *
* */ public Enumeration enumerateAttributeNames() { return this.attributes.keys(); } /** * Enumerates the child elements. * *
Postconditions:
*
  • result != null *
* */ public Enumeration enumerateChildren() { return this.children.elements(); } /** * Returns the PCDATA content of the object. If there is no such content, * null is returned. * */ public String getContent() { return this.contents; } /** * Returns the line nr in the source data on which the element is found. * This method returns 0 there is no associated source data. * *
Postconditions:
*
  • {@code result >= 0} *
*/ public int getLineNr() { return this.lineNr; } /** * 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 *
* */ public Object getAttribute(String name) { if (this.ignoreCase) { name = name.toUpperCase(); } Object value = this.attributes.get(name); return value; } /** * Returns the name of the element. * */ 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 net.sourceforge.nanoxml.XMLParseException * If an error occured while parsing the read data. */ public void parseFromReader(Reader reader) throws IOException, XMLParseException { this.parseFromReader(reader, /*startingLineNr*/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 startingLineNr * 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 net.sourceforge.nanoxml.XMLParseException * If an error occured while parsing the read data. */ public void parseFromReader(Reader reader, int startingLineNr) throws IOException, XMLParseException { this.charReadTooMuch = '\0'; this.reader = reader; this.parserLineNr = startingLineNr; for (;;) { char ch = this.scanWhitespace(); if (ch != '<') { throw this.expectedInput("<", ch); } ch = this.readChar(); if ((ch == '!') || (ch == '?')) { this.skipSpecialTag(0); } else { this.unreadChar(ch); this.scanElement(this); return; } } } /** * 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) { this.contents = content; } /** * Changes the name of the element. * * @param name * The new name. * *

Preconditions:
*
  • name != null *
  • name is a valid XML identifier *
* */ public void setName(String name) { this.name = name; } /** * 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); } 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 false; } else if (!this.checkLiteral("CDATA[")) { this.skipSpecialTag(1); // one [ has already been read 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; } // Be more tolerant of extra -- (double dashes) // in comments. if (dashesToRead == 0) { ch = this.readChar(); if (ch == '>') { return; } else { dashesToRead = 2; this.unreadChar(ch); } } } /* if (this.readChar() != '>') { throw this.expectedInput(">"); } */ } /** * 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)) { 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.parserLineNr += 1; return '\n'; } else { 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 name = buf.toString(); elt.setName(name); char ch = this.scanWhitespace(); while ((ch != '>') && (ch != '/')) { buf.setLength(0); this.unreadChar(ch); this.scanIdentifier(buf); String key = buf.toString(); ch = this.scanWhitespace(); if (ch != '=') { throw this.expectedInput("="); } this.unreadChar(this.scanWhitespace()); buf.setLength(0); this.scanString(buf); elt.setAttribute(key, buf); ch = this.scanWhitespace(); } if (ch == '/') { ch = this.readChar(); if (ch != '>') { throw this.expectedInput(">"); } return; } buf.setLength(0); ch = this.scanWhitespace(buf); 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 { buf.setLength(0); 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(name)) { throw this.expectedInput(name); } 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); } 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 = entities.get(key); if (value == null) { throw this.unknownEntity(key); } 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.parserLineNr, 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.parserLineNr, 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.parserLineNr, 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.parserLineNr, 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.parserLineNr, 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. * @param ch The character that was received instead. *
Preconditions:
*
  • charSet != null *
  • charSet.length() > 0 *
*/ protected XMLParseException expectedInput(String charSet, char ch) { String msg = "Expected: '" + charSet + "'" + " but got: '" + ch + "'"; return new XMLParseException(this.getName(), this.parserLineNr, 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.parserLineNr, msg); } /** * Reads an xml file and removes the comments, leaving only relevant * xml code. * * @param isr The reader of the InputStream containing the xml. * @param pout The PipedOutputStream that will be receiving the filtered * xml file. */ public void sanitizeInput(Reader isr, OutputStream pout) { try { PrintStream out = new PrintStream(pout); this.sanitizeCharReadTooMuch = '\0'; this.reader = isr; this.parserLineNr = 0; int newline = 2; char prev = ' '; while (true) { char ch; if (this.sanitizeCharReadTooMuch != '\0') { ch = this.sanitizeCharReadTooMuch; this.sanitizeCharReadTooMuch = '\0'; } else { int i = this.reader.read(); if (i == -1) { // no character in buffer, and nothing read out.flush(); break; } else if (i == 10) { ch = '\n'; } else { ch = (char) i; } } char next; int i = this.reader.read(); if (i == -1) { // character in buffer and nothing read. write out // what's in the buffer out.print(ch); out.flush(); if (JNLPRuntime.isDebug()) { if (ch == 10) { OutputController.getLogger().printOutLn(""); OutputController.getLogger().printOut("line: " + newline + " "); newline++; } else { OutputController.getLogger().printOut(ch+""); } } break; } else if (i == 10) { next = '\n'; } else { next = (char) i; } this.sanitizeCharReadTooMuch = next; // If the next chars are !--, then we've hit a comment tag, // and should skip it. if (ch == '<' && sanitizeCharReadTooMuch == '!') { ch = (char) this.reader.read(); if (ch == '-') { ch = (char) this.reader.read(); if (ch == '-') { this.skipComment(); this.sanitizeCharReadTooMuch = '\0'; } else { out.print('<'); out.print('!'); out.print('-'); this.sanitizeCharReadTooMuch = ch; if (JNLPRuntime.isDebug()) { OutputController.getLogger().printOut("<"); OutputController.getLogger().printOut("!"); OutputController.getLogger().printOut("-"); } } } else { out.print('<'); out.print('!'); this.sanitizeCharReadTooMuch = ch; if (JNLPRuntime.isDebug()) { OutputController.getLogger().printOut("<"); OutputController.getLogger().printOut("!"); } } } // Otherwise we haven't hit a comment, and we should write ch. else { out.print(ch); if (JNLPRuntime.isDebug()) { if (ch == 10) { OutputController.getLogger().printOutLn(""); OutputController.getLogger().printOut("line: " + newline + " "); newline++; } else { OutputController.getLogger().printOut(ch+""); } } } prev = next; } out.close(); isr.close(); } catch (Exception e) { // Print the stack trace here -- xml.parseFromReader() will // throw the ParseException if something goes wrong. OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); } } }