Java tutorial
/* * $Id: XMLFormatter.java,v 1.3 2004/05/28 09:14:00 edankert Exp $ * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the License. * * The Original Code is eXchaNGeR browser code. (org.xngr.browser.*) * * The Initial Developer of the Original Code is Cladonia Ltd.. Portions created * by the Initial Developer are Copyright (C) 2003 the Initial Developer. * All Rights Reserved. * * Contributor(s): Dogsbay */ package com.cladonia.xml; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.List; import java.util.StringTokenizer; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentType; import org.dom4j.Element; import org.dom4j.Entity; import org.dom4j.Namespace; import org.dom4j.Node; import org.dom4j.ProcessingInstruction; import org.dom4j.Text; import org.dom4j.dtd.InternalEntityDecl; import org.dom4j.io.XMLWriter; import org.dom4j.tree.NamespaceStack; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /**<p><code>XMLWriter</code> takes a DOM4J tree and formats it to a * stream as XML. * It can also take SAX events too so can be used by SAX clients as this object * implements the {@link ContentHandler} and {@link LexicalHandler} interfaces. * as well. This formatter performs typical document * formatting. The XML declaration and processing instructions are * always on their own lines. An {@link OutputFormat} object can be * used to define how whitespace is handled when printing and allows various * configuration options, such as to allow suppression of the XML declaration, * the encoding declaration or whether empty documents are collapsed.</p> * * <p> There are <code>write(...)</code> methods to print any of the * standard DOM4J classes, including <code>Document</code> and * <code>Element</code>, to either a <code>Writer</code> or an * <code>OutputStream</code>. Warning: using your own * <code>Writer</code> may cause the writer's preferred character * encoding to be ignored. If you use encodings other than UTF8, we * recommend using the method that takes an OutputStream instead. * </p> * * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> * @author Joseph Bowbeer * @version $Revision: 1.3 $ */ public class XMLFormatter extends XMLWriter { private static final boolean DEBUG = false; /** The Stack of namespaceStack written so far */ private NamespaceStack namespaceStack = new NamespaceStack(); private int indentLevel = 0; private ExchangerOutputFormat format = null; private int lineLength = -1; private boolean wrapText = false; boolean preserveMixedContent = false; boolean defaultTrimText = false; // public XMLFormatter( Writer writer, OutputFormat format) throws UnsupportedEncodingException { // super( new LineWriter( writer), format); // this.format = format; // namespaceStack.push(Namespace.NO_NAMESPACE); // } public XMLFormatter(OutputStream out, ExchangerOutputFormat format) throws UnsupportedEncodingException { super(out, format); this.format = format; namespaceStack.push(Namespace.NO_NAMESPACE); } public XMLFormatter(ExchangerOutputFormat format) throws UnsupportedEncodingException { super(format); this.format = format; namespaceStack.push(Namespace.NO_NAMESPACE); } /** * Get an OutputStreamWriter, use preferred encoding. */ protected Writer createWriter(OutputStream outStream, String encoding) throws UnsupportedEncodingException { return new LineWriter(super.createWriter(outStream, XMLUtilities.mapXMLEncodingToJava(encoding))); } public void setMaxLineLength(int length) { this.lineLength = length; } public int getMaxLineLength() { return this.lineLength; } /** Set the initial indentation level. This can be used to output * a document (or, more likely, an element) starting at a given * indent level, so it's not always flush against the left margin. * Default: 0 * * @param indentLevel the number of indents to start with */ public void setIndentLevel(int indentLevel) { this.indentLevel = indentLevel; } public void setPreserveMixedContent(boolean preserve) { if (DEBUG) System.out.println("XMLFormatter.setPreserveMixedContent( " + preserve + ")"); preserveMixedContent = preserve; } public boolean isPreserveMixedContent() { return preserveMixedContent; } public void setTrimText(boolean trim) { if (DEBUG) System.out.println("XMLFormatter.setTrimText( " + trim + ")"); defaultTrimText = trim; format.setTrimText(trim); } public boolean isTrimText() { return defaultTrimText; } /** * <p> * This will write the declaration to the given Writer. * Assumes XML version 1.0 since we don't directly know. * </p> */ protected void writeDeclaration() throws IOException { String encoding = format.getEncoding(); String version = format.getVersion(); String standalone = format.getStandalone(); // Only print of declaration is not suppressed if (!format.isSuppressDeclaration()) { // Assume 1.0 version writer.write("<?xml version=\"" + version + "\""); if (!format.isOmitEncoding()) { writer.write(" encoding=\"" + encoding + "\""); } if (!format.isOmitStandalone()) { writer.write(" standalone=\"" + standalone + "\""); } writer.write("?>"); println(); } } public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException { if (DEBUG) System.out.println("XMLFormatter.startElement( " + namespaceURI + ", " + localName + ", " + qName + ", " + attributes + ")"); try { writePrintln(); indent(); writer.write("<"); writer.write(qName); writeNamespaces(); writeAttributes(attributes); writer.write(">"); ++indentLevel; lastOutputNodeType = Node.ELEMENT_NODE; super.startElement(namespaceURI, localName, qName, attributes); } catch (IOException e) { handleException(e); } } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { if (DEBUG) System.out.println("XMLFormatter.endElement( " + namespaceURI + ", " + localName + ", " + qName + ")"); try { --indentLevel; if (lastOutputNodeType == Node.ELEMENT_NODE) { writePrintln(); indent(); } // XXXX: need to determine this using a stack and checking for // content / children boolean hadContent = true; if (hadContent) { writeClose(qName); } else { writeEmptyElementClose(qName); } lastOutputNodeType = Node.ELEMENT_NODE; super.endElement(namespaceURI, localName, qName); } catch (IOException e) { handleException(e); } } protected void writeDocType(DocumentType docType) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writeDocType( " + docType + ")"); if (docType != null) { List list = docType.getInternalDeclarations(); if (list != null) { for (int i = 0; i < list.size(); i++) { Object internalDecl = list.get(i); if (internalDecl instanceof InternalEntityDecl) { InternalEntityDecl decl = (InternalEntityDecl) internalDecl; String value = decl.getValue(); StringBuffer result = new StringBuffer("&"); int index = value.indexOf("&"); if (index != -1 && value.length() > 5) { if (index > 0) { result.append(value.substring(0, index + 1)); } result.append(value.substring(index + 5, value.length())); decl.setValue(result.toString()); } } } } docType.write(writer); writer.write(format.getLineSeparator()); writer.write(format.getLineSeparator()); } } protected void writeEntity(Entity entity) throws IOException { String name = entity.getName(); if (DEBUG) System.out.println("XMLFormatter.writeEntity( " + name + ")"); if (!name.equals("amp")) { // Should never happen. writeEntityRef(entity.getName()); } else { writer.write("&"); } } protected void writeEntityRef(String name) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writeEntityRef( " + name + ")"); // if ( !name.equals( "amp")) { // Should never happen. // writeEntityRef( entity.getName()); // } else { // writer.write( "&"); // } } protected String escapeElementEntities(String text) { if (DEBUG) System.out.println("XMLFormatter.escapeElementEntities( " + text + ")"); // No automatic entity escaping! return text; } protected String escapeAttributeEntities(String text) { if (DEBUG) System.out.println("XMLFormatter.escapeAttributeEntities( " + text + ")"); // No automatic entity escaping! return text; } // Implementation methods //------------------------------------------------------------------------- protected void writeElement(Element element) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writeElement( " + element + ")"); // if ( indentMixed) { // super.writeElement( element); // } else { int size = element.nodeCount(); String qualifiedName = element.getQualifiedName(); boolean hasElement = false; boolean hasText = false; // first test to see if this element has mixed content, // if whitespace is significant ... for (int i = 0; i < size; i++) { Node node = element.node(i); if (node instanceof Element) { hasElement = true; } else if (node instanceof Text) { String text = node.getText(); if (text != null && text.trim().length() > 0) { hasText = true; } } } Attribute space = element.attribute("space"); boolean preserveSpace = false; if (space != null) { String prefix = space.getNamespacePrefix(); String value = space.getValue(); // System.out.println( "prefix = "+prefix+" value = "+value); if (prefix != null && "xml".equals(prefix) && "preserve".equals(value)) { preserveSpace = true; } } writePrintln(); indent(); writer.write("<"); writer.write(qualifiedName); int previouslyDeclaredNamespaces = namespaceStack.size(); Namespace ns = element.getNamespace(); if (isNamespaceDeclaration(ns)) { namespaceStack.push(ns); writeNamespace(ns); } // Print out additional namespace declarations for (int i = 0; i < size; i++) { Node node = element.node(i); if (node instanceof Namespace) { Namespace additional = (Namespace) node; if (isNamespaceDeclaration(additional)) { namespaceStack.push(additional); writeNamespace(additional); } } } writeAttributes(element); lastOutputNodeType = Node.ELEMENT_NODE; if (size <= 0) { writeEmptyElementClose(qualifiedName); } else { writer.write(">"); if (!hasElement && !preserveSpace) { // text only // we have at least one text node so lets assume // that its non-empty // System.out.println( "writeElementContent (Text) ..."); boolean previousWrapText = wrapText; wrapText = true; writeElementContent(element); wrapText = previousWrapText; } else if (preserveMixedContent && hasElement && hasText) { // preserve space // Mixed content // System.out.println( "writeMixedElementContent ..."); Node lastNode = writeMixedElementContent(element); } else if (preserveSpace) { // preserve space // Mixed content // System.out.println( "writePreserveElementContent ..."); Node lastNode = writePreservedElementContent(element); } else { // hasElement && !hasText // System.out.println( "writeElementContent (Element) ..."); boolean previousWrapText = wrapText; wrapText = true; ++indentLevel; writeElementContent(element); --indentLevel; wrapText = previousWrapText; writePrintln(); indent(); } writer.write("</"); writer.write(qualifiedName); writer.write(">"); // writePrintln(); } // remove declared namespaceStack from stack while (namespaceStack.size() > previouslyDeclaredNamespaces) { namespaceStack.pop(); } lastOutputNodeType = Node.ELEMENT_NODE; // } } protected void writeMixedElement(Element element) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writeMixedElement( " + element + ")"); int size = element.nodeCount(); String qualifiedName = element.getQualifiedName(); boolean previousTrim = format.isTrimText(); boolean previousWrapText = wrapText; if (isPreserveMixedContent()) { format.setTrimText(false); wrapText = false; } else { wrapText = true; } writer.write("<"); writer.write(qualifiedName); int previouslyDeclaredNamespaces = namespaceStack.size(); Namespace ns = element.getNamespace(); if (isNamespaceDeclaration(ns)) { namespaceStack.push(ns); writeNamespace(ns); } // Print out additional namespace declarations boolean textOnly = true; for (int i = 0; i < size; i++) { Node node = element.node(i); if (node instanceof Namespace) { Namespace additional = (Namespace) node; if (isNamespaceDeclaration(additional)) { namespaceStack.push(additional); writeNamespace(additional); } } else if (node instanceof Element) { textOnly = false; } } writeAttributes(element); lastOutputNodeType = Node.ELEMENT_NODE; if (size <= 0) { writeEmptyElementClose(qualifiedName); // writePrintln(); } else { writer.write(">"); writeMixedElementContent(element); writer.write("</"); writer.write(qualifiedName); writer.write(">"); } // remove declared namespaceStack from stack while (namespaceStack.size() > previouslyDeclaredNamespaces) { namespaceStack.pop(); } lastOutputNodeType = Node.ELEMENT_NODE; format.setTrimText(previousTrim); wrapText = previousWrapText; } protected void writePreservedElement(Element element) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writePreservedElement( " + element + ")"); int size = element.nodeCount(); String qualifiedName = element.getQualifiedName(); boolean previousTrim = format.isTrimText(); boolean previousWrapText = wrapText; format.setTrimText(false); wrapText = false; boolean hasElement = false; boolean hasText = false; // first test to see if this element has mixed content, // if whitespace is significant ... for (int i = 0; i < size; i++) { Node node = element.node(i); if (node instanceof Element) { hasElement = true; } else if (node instanceof Text) { String text = node.getText(); if (text != null && text.trim().length() > 0) { hasText = true; } } } Attribute space = element.attribute("space"); boolean defaultSpace = false; if (space != null) { String prefix = space.getNamespacePrefix(); String value = space.getValue(); // System.out.println( "prefix = "+prefix+" value = "+value); if (prefix != null && "xml".equals(prefix) && "default".equals(value)) { defaultSpace = true; } } writer.write("<"); writer.write(qualifiedName); int previouslyDeclaredNamespaces = namespaceStack.size(); Namespace ns = element.getNamespace(); if (isNamespaceDeclaration(ns)) { namespaceStack.push(ns); writeNamespace(ns); } // Print out additional namespace declarations for (int i = 0; i < size; i++) { Node node = element.node(i); if (node instanceof Namespace) { Namespace additional = (Namespace) node; if (isNamespaceDeclaration(additional)) { namespaceStack.push(additional); writeNamespace(additional); } } } writeAttributes(element); lastOutputNodeType = Node.ELEMENT_NODE; if (size <= 0) { writeEmptyElementClose(qualifiedName); } else { writer.write(">"); if (preserveMixedContent && hasElement && hasText) { // mixed content // System.out.println( "writeMixedElementContent ..."); Node lastNode = writeMixedElementContent(element); } else if (!defaultSpace) { // preserve space // System.out.println( "writePreservedElementContent ..."); Node lastNode = writePreservedElementContent(element); } else { // System.out.println( "writeElementContent ..."); format.setTrimText(isTrimText()); boolean prevWrapText = wrapText; wrapText = true; writeElementContent(element); wrapText = prevWrapText; format.setTrimText(false); } writer.write("</"); writer.write(qualifiedName); writer.write(">"); } // remove declared namespaceStack from stack while (namespaceStack.size() > previouslyDeclaredNamespaces) { namespaceStack.pop(); } lastOutputNodeType = Node.ELEMENT_NODE; format.setTrimText(previousTrim); wrapText = previousWrapText; } /** Outputs the content of the given element. If whitespace trimming is * enabled then all adjacent text nodes are appended together before * the whitespace trimming occurs to avoid problems with multiple * text nodes being created due to text content that spans parser buffers * in a SAX parser. */ protected Node writeMixedElementContent(Element element) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writeMixedElementContent( " + element + ")"); Node previousNode = null; boolean previousTrim = format.isTrimText(); boolean previousWrapText = wrapText; if (isPreserveMixedContent()) { format.setTrimText(false); wrapText = false; } else { wrapText = true; } for (int i = 0, size = element.nodeCount(); i < size; i++) { Node node = element.node(i); Node nextNode = null; if (i + 1 < size) { nextNode = element.node(i + 1); } if (node instanceof Text) { writeString(node.getText()); } else { writeMixedNode(node); } previousNode = node; } format.setTrimText(previousTrim); wrapText = previousWrapText; return previousNode; } protected void writeString(String text) throws IOException { if (text != null && text.length() > 0) { if (format.isTrimText()) { boolean first = true; StringTokenizer tokenizer = new StringTokenizer(text); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (wrapText && lineLength != -1 && ((((LineWriter) writer).getColumn() + token.length()) > lineLength)) { indentLevel++; writePrintln(); indent(); indentLevel--; if (first) { first = false; } } else if (first) { first = false; if (lastOutputNodeType == Node.TEXT_NODE) { writer.write(" "); } else if (format.isPadText() && lastOutputNodeType == Node.ELEMENT_NODE) { writer.write(" "); } } else { writer.write(" "); } writer.write(token); lastOutputNodeType = Node.TEXT_NODE; } } else { lastOutputNodeType = Node.TEXT_NODE; writer.write(text); } } } protected Node writePreservedElementContent(Element element) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writePreservedElementContent( " + element + ")"); Node previousNode = null; boolean previousTrim = format.isTrimText(); boolean previousWrapText = wrapText; format.setTrimText(false); wrapText = false; for (int i = 0, size = element.nodeCount(); i < size; i++) { Node node = element.node(i); Node nextNode = null; if (i + 1 < size) { nextNode = element.node(i + 1); } if (node instanceof Text) { String text = node.getText(); // previous node was an empty element-node <element/> writeString(text); } else { writePreservedNode(node); } previousNode = node; } format.setTrimText(previousTrim); wrapText = previousWrapText; return previousNode; } protected void writeMixedNode(Node node) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writeMixedNode( " + node + ")"); int nodeType = node.getNodeType(); switch (nodeType) { case Node.ELEMENT_NODE: writeMixedElement((Element) node); break; case Node.ATTRIBUTE_NODE: writeAttribute((Attribute) node); break; case Node.TEXT_NODE: writeString(node.getText()); //write((Text) node); break; case Node.CDATA_SECTION_NODE: writeCDATA(node.getText()); break; case Node.ENTITY_REFERENCE_NODE: writeEntity((Entity) node); break; case Node.PROCESSING_INSTRUCTION_NODE: writeProcessingInstruction((ProcessingInstruction) node); break; case Node.COMMENT_NODE: writeComment(node.getText()); break; case Node.DOCUMENT_NODE: write((Document) node); break; case Node.DOCUMENT_TYPE_NODE: writeDocType((DocumentType) node); break; case Node.NAMESPACE_NODE: // Will be output with attributes //write((Namespace) node); break; default: throw new IOException("Invalid node type: " + node); } } protected void writePreservedNode(Node node) throws IOException { if (DEBUG) System.out.println("XMLFormatter.writeMixedNode( " + node + ")"); int nodeType = node.getNodeType(); switch (nodeType) { case Node.ELEMENT_NODE: writePreservedElement((Element) node); break; case Node.ATTRIBUTE_NODE: writeAttribute((Attribute) node); break; case Node.TEXT_NODE: writeString(node.getText()); //write((Text) node); break; case Node.CDATA_SECTION_NODE: writeCDATA(node.getText()); break; case Node.ENTITY_REFERENCE_NODE: writeEntity((Entity) node); break; case Node.PROCESSING_INSTRUCTION_NODE: writeProcessingInstruction((ProcessingInstruction) node); break; case Node.COMMENT_NODE: writeComment(node.getText()); break; case Node.DOCUMENT_NODE: write((Document) node); break; case Node.DOCUMENT_TYPE_NODE: writeDocType((DocumentType) node); break; case Node.NAMESPACE_NODE: // Will be output with attributes //write((Namespace) node); break; default: throw new IOException("Invalid node type: " + node); } } /** Writes the attributes of the given element * */ // protected void writeAttributes( Element element ) throws IOException { // if (DEBUG) System.out.println( "XMLFormatter.writeAttributes( "+element+")"); // if (indentMixed) { // super.writeAttributes( element); // } else { // I do not yet handle the case where the same prefix maps to // two different URIs. For attributes on the same element // this is illegal; but as yet we don't throw an exception // if someone tries to do this // for ( int i = 0, size = element.attributeCount(); i < size; i++ ) { // Attribute attribute = element.attribute(i); // Namespace ns = attribute.getNamespace(); // if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) { // String prefix = ns.getPrefix(); // String uri = namespaceStack.getURI(prefix); // if (!ns.getURI().equals(uri)) { // output a new namespace declaration // writeNamespace(ns); // namespaceStack.push(ns); // } // } // // writer.write(" "); // writer.write(attribute.getQualifiedName()); // writer.write("=\""); // writeEscapeAttributeEntities(attribute.getValue()); // writer.write("\""); // } //// } // } protected void writeAttribute(Attribute attribute) throws IOException { XAttribute a = (XAttribute) attribute; // if ( attributeOnNewLine) { // ++indentLevel; // // format.setNewlines( true); // format.setIndent( indentString); // // writePrintln(); // indent(); // } else { writer.write(" "); // } writer.write(attribute.getQualifiedName()); writer.write("="); String value = attribute.getValue(); if (format.isTrimText() && value != null && value.trim().length() > 0) { StringTokenizer tokenizer = new StringTokenizer(value); StringBuffer buffer = new StringBuffer(); boolean first = true; while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (!first) { buffer.append(" "); } buffer.append(token); first = false; } value = buffer.toString(); } boolean hasQuote = value.indexOf("\"") != -1; if (hasQuote) { writer.write("'"); writeEscapeAttributeEntities(value); writer.write("'"); } else { writer.write("\""); writeEscapeAttributeEntities(value); writer.write("\""); } lastOutputNodeType = Node.ATTRIBUTE_NODE; // reset... // if ( attributeOnNewLine) { // --indentLevel; // // format.setNewlines( false); // format.setIndent( ""); // } } protected void writeAttributes(Element element) throws IOException { // I do not yet handle the case where the same prefix maps to // two different URIs. For attributes on the same element // this is illegal; but as yet we don't throw an exception // if someone tries to do this for (int i = 0, size = element.attributeCount(); i < size; i++) { Attribute attribute = element.attribute(i); Namespace ns = attribute.getNamespace(); if (ns != null && ns != Namespace.NO_NAMESPACE && ns != Namespace.XML_NAMESPACE) { String prefix = ns.getPrefix(); String uri = namespaceStack.getURI(prefix); if (!ns.getURI().equals(uri)) { // output a new namespace declaration writeNamespace(ns); namespaceStack.push(ns); } } writeAttribute(attribute); } } protected void writeNamespace(Namespace namespace) throws IOException { if (namespace != null) { writeNamespace(namespace.getPrefix(), namespace.getURI()); } } protected void writeNamespace(String prefix, String uri) throws IOException { // if ( attributeOnNewLine) { // ++indentLevel; // // format.setNewlines( true); // format.setIndent( indentString); // // writePrintln(); // indent(); // } else { writer.write(" "); // } if (prefix != null && prefix.length() > 0) { writer.write("xmlns:"); writer.write(prefix); writer.write("=\""); } else { writer.write("xmlns=\""); } writer.write(uri); writer.write("\""); // reset... // if ( attributeOnNewLine) { // --indentLevel; // // format.setNewlines( false); // format.setIndent( ""); // } } protected void indent() throws IOException { if (DEBUG) System.out.println("XMLFormatter.indent()"); // if (indentMixed) { // super.indent(); // } else { String indent = getOutputFormat().getIndent(); if (indent != null && indent.length() > 0) { for (int i = 0; i < indentLevel; i++) { writer.write(indent); } } // } } protected boolean isNamespaceDeclaration(Namespace ns) { if (DEBUG) System.out.println("XMLFormatter.isNamespaceDeclaration( " + ns + ")"); // if (indentMixed) { // return super.isNamespaceDeclaration( ns); // } else if (ns != null && ns != Namespace.XML_NAMESPACE) { String uri = ns.getURI(); if (uri != null) { if (!namespaceStack.contains(ns)) { return true; } } // } return false; } private String trimStart(String string) { int len = string.length(); char[] val = string.toCharArray(); /* avoid getfield opcode */ int index = 0; while ((index < len) && (val[index] <= ' ')) { index++; } return (index > 0) ? string.substring(index, len) : string; } private String trimEnd(String string) { int len = string.length(); char[] val = string.toCharArray(); /* avoid getfield opcode */ while ((len > 0) && (val[len - 1] <= ' ')) { len--; } return ((len < string.length())) ? string.substring(0, len) : string; } private String trimSpaces(String string) { StringBuffer buffer = new StringBuffer(); int len = string.length(); char[] val = string.toCharArray(); /* avoid getfield opcode */ // trim spaces till first normal character. for (int i = 0; i < val.length; i++) { if ((val[i] < ' ')) { buffer.append(val[i]); } else if (val[i] > ' ') { break; } } buffer.append(string.trim()); // trim spaces till last normal character. for (int i = val.length; i > 0; i--) { if ((val[i - 1] < ' ')) { buffer.append(val[i - 1]); } else if (val[i - 1] > ' ') { break; } } return buffer.toString(); } private class LineWriter extends Writer { private int column = 0; private Writer out; public LineWriter(Writer out) { this.out = out; } public int getColumn() { return column; } public void close() throws IOException { out.close(); } public void flush() throws IOException { out.flush(); } public void write(char[] cbuf, int off, int len) throws IOException { for (int i = off; i < (off + len); i++) { write(cbuf[i]); } } public void write(int c) throws IOException { column++; if ((char) c == '\n') { column = 0; } out.write(c); // if ( Character.isWhitespace( (char)c)) { // if ( (char)c == '\n') { // column = 0; // // out.write( c); // } else if ( wrapText && lineLength != -1 && format.isNewlines() && column >= lineLength) { // column = 0; // // writePrintln(); // // indentLevel++; // indent(); // indentLevel--; // } else { // out.write( c); // } // } else { // out.write( c); // } } public void write(String s, int off, int len) throws IOException { write(s.toCharArray(), off, len); } public void write(char[] buf) throws IOException { write(buf, 0, buf.length); } public void write(String s) throws IOException { write(s.toCharArray(), 0, s.length()); } } }