/* * Copyright 2000-2005 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.util; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; /** * Writes a DOM tree to a given Writer. * *

Utility class used by {@link org.apache.tools.ant.XmlLogger * XmlLogger} and * org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter * XMLJUnitResultFormatter}.

* */ public class DOMElementWriter { private static String lSep = System.getProperty("line.separator"); /** * Don't try to be too smart but at least recognize the predefined * entities. */ protected String[] knownEntities = {"gt", "amp", "lt", "apos", "quot"}; /** * Writes a DOM tree to a stream in UTF8 encoding. Note that * it prepends the <?xml version='1.0' encoding='UTF-8'?>. * The indent number is set to 0 and a 2-space indent. * @param root the root element of the DOM tree. * @param out the outputstream to write to. * @throws IOException if an error happens while writing to the stream. */ public void write(Element root, OutputStream out) throws IOException { Writer wri = new OutputStreamWriter(out, "UTF8"); wri.write("\n"); write(root, wri, 0, " "); wri.flush(); } /** * Writes a DOM tree to a stream. * * @param element the Root DOM element of the tree * @param out where to send the output * @param indent number of * @param indentWith string that should be used to indent the corresponding tag. * @throws IOException if an error happens while writing to the stream. */ public void write(Element element, Writer out, int indent, String indentWith) throws IOException { openElement(element, out, indent, indentWith); // Write child elements and text boolean hasChildren = false; NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); switch (child.getNodeType()) { case Node.ELEMENT_NODE: if (!hasChildren) { out.write(lSep); hasChildren = true; } write((Element) child, out, indent + 1, indentWith); break; case Node.TEXT_NODE: out.write(encode(child.getNodeValue())); break; case Node.COMMENT_NODE: out.write(""); break; case Node.CDATA_SECTION_NODE: out.write(""); break; case Node.ENTITY_REFERENCE_NODE: out.write('&'); out.write(child.getNodeName()); out.write(';'); break; case Node.PROCESSING_INSTRUCTION_NODE: out.write(" 0) { out.write(' '); out.write(data); } out.write("?>"); break; } } closeElement(element, out, indent, indentWith, hasChildren); } /** * Writes the opening tag - including all attributes - * correspondong to a DOM element. * * @param element the DOM element to write * @param out where to send the output * @param indent number of * @param indentWith string that should be used to indent the * corresponding tag. * @throws IOException if an error happens while writing to the stream. */ public void openElement(Element element, Writer out, int indent, String indentWith) throws IOException { // Write indent characters for (int i = 0; i < indent; i++) { out.write(indentWith); } // Write element out.write("<"); out.write(element.getTagName()); // Write attributes NamedNodeMap attrs = element.getAttributes(); for (int i = 0; i < attrs.getLength(); i++) { Attr attr = (Attr) attrs.item(i); out.write(" "); out.write(attr.getName()); out.write("=\""); out.write(encode(attr.getValue())); out.write("\""); } out.write(">"); } /** * Writes a DOM tree to a stream. * * @param element the Root DOM element of the tree * @param out where to send the output * @param indent number of * @param indentWith string that should be used to indent the * corresponding tag. * @throws IOException if an error happens while writing to the stream. */ public void closeElement(Element element, Writer out, int indent, String indentWith, boolean hasChildren) throws IOException { // If we had child elements, we need to indent before we close // the element, otherwise we're on the same line and don't need // to indent if (hasChildren) { for (int i = 0; i < indent; i++) { out.write(indentWith); } } // Write element close out.write(""); out.write(lSep); out.flush(); } /** * Escape <, > & ', " as their entities and * drop characters that are illegal in XML documents. */ public String encode(String value) { StringBuffer sb = new StringBuffer(); int len = value.length(); for (int i = 0; i < len; i++) { char c = value.charAt(i); switch (c) { case '<': sb.append("<"); break; case '>': sb.append(">"); break; case '\'': sb.append("'"); break; case '\"': sb.append("""); break; case '&': int nextSemi = value.indexOf(";", i); if (nextSemi < 0 || !isReference(value.substring(i, nextSemi + 1))) { sb.append("&"); } else { sb.append('&'); } break; default: if (isLegalCharacter(c)) { sb.append(c); } break; } } return sb.substring(0); } /** * Drop characters that are illegal in XML documents. * *

Also ensure that we are not including an ]]> * marker by replacing that sequence with * &#x5d;&#x5d;&gt;.

* *

See XML 1.0 2.2 http://www.w3.org/TR/1998/REC-xml-19980210#charsets and * 2.7 http://www.w3.org/TR/1998/REC-xml-19980210#sec-cdata-sect.

*/ public String encodedata(final String value) { StringBuffer sb = new StringBuffer(); int len = value.length(); for (int i = 0; i < len; ++i) { char c = value.charAt(i); if (isLegalCharacter(c)) { sb.append(c); } } String result = sb.substring(0); int cdEnd = result.indexOf("]]>"); while (cdEnd != -1) { sb.setLength(cdEnd); sb.append("]]>") .append(result.substring(cdEnd + 3)); result = sb.substring(0); cdEnd = result.indexOf("]]>"); } return result; } /** * Is the given argument a character or entity reference? */ public boolean isReference(String ent) { if (!(ent.charAt(0) == '&') || !ent.endsWith(";")) { return false; } if (ent.charAt(1) == '#') { if (ent.charAt(2) == 'x') { try { Integer.parseInt(ent.substring(3, ent.length() - 1), 16); return true; } catch (NumberFormatException nfe) { return false; } } else { try { Integer.parseInt(ent.substring(2, ent.length() - 1)); return true; } catch (NumberFormatException nfe) { return false; } } } String name = ent.substring(1, ent.length() - 1); for (int i = 0; i < knownEntities.length; i++) { if (name.equals(knownEntities[i])) { return true; } } return false; } /** * Is the given character allowed inside an XML document? * *

See XML 1.0 2.2 * http://www.w3.org/TR/1998/REC-xml-19980210#charsets.

* * @since 1.10, Ant 1.5 */ public boolean isLegalCharacter(char c) { if (c == 0x9 || c == 0xA || c == 0xD) { return true; } else if (c < 0x20) { return false; } else if (c <= 0xD7FF) { return true; } else if (c < 0xE000) { return false; } else if (c <= 0xFFFD) { return true; } return false; } }