new class. Wrap nested text into <![CDATA[ ]]> and replace special characters in attribute values correctly. PR: 413 git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268330 13f79535-47bb-0310-9956-ffa450edef68master
@@ -58,6 +58,7 @@ import java.io.*; | |||
import java.util.*; | |||
import javax.xml.parsers.*; | |||
import org.w3c.dom.*; | |||
import org.apache.tools.ant.util.DOMElementWriter; | |||
/** | |||
* Generates a "log.xml" file in the current directory with | |||
@@ -134,7 +135,7 @@ public class XmlLogger implements BuildListener { | |||
Writer out = new FileWriter(outFilename); | |||
out.write("<?xml:stylesheet type=\"text/xsl\" href=\"log.xsl\"?>\n\n"); | |||
write(buildElement, out, 0); | |||
(new DOMElementWriter()).write(buildElement, out, 0, "\t"); | |||
out.flush(); | |||
out.close(); | |||
@@ -228,63 +229,4 @@ public class XmlLogger implements BuildListener { | |||
} | |||
} | |||
/** | |||
* Writes a DOM element to a file. | |||
*/ | |||
private static void write(Element element, Writer out, int indent) throws IOException { | |||
// Write indent characters | |||
for (int i = 0; i < indent; i++) { | |||
out.write("\t"); | |||
} | |||
// 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(attr.getValue()); | |||
out.write("\""); | |||
} | |||
out.write(">"); | |||
// Write child attributes and text | |||
boolean hasChildren = false; | |||
NodeList children = element.getChildNodes(); | |||
for (int i = 0; i < children.getLength(); i++) { | |||
Node child = children.item(i); | |||
if (child.getNodeType() == Node.ELEMENT_NODE) { | |||
if (!hasChildren) { | |||
out.write("\n"); | |||
hasChildren = true; | |||
} | |||
write((Element)child, out, indent + 1); | |||
} | |||
if (child.getNodeType() == Node.TEXT_NODE) { | |||
out.write(((Text)child).getData()); | |||
} | |||
} | |||
// 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("\t"); | |||
} | |||
} | |||
// Write element close | |||
out.write("</"); | |||
out.write(element.getTagName()); | |||
out.write(">\n"); | |||
} | |||
} |
@@ -23,7 +23,7 @@ | |||
* Alternately, this acknowlegement may appear in the software itself, | |||
* if and wherever such third-party acknowlegements normally appear. | |||
* | |||
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||
* 4. The names "The Jakarta Project", "Ant", and "Apache Software | |||
* Foundation" must not be used to endorse or promote products derived | |||
* from this software without prior written permission. For written | |||
* permission, please contact apache@apache.org. | |||
@@ -62,6 +62,7 @@ import java.util.*; | |||
import javax.xml.parsers.*; | |||
import org.w3c.dom.*; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.util.DOMElementWriter; | |||
import junit.framework.Test; | |||
import junit.framework.TestCase; | |||
@@ -69,7 +70,7 @@ import junit.framework.TestCase; | |||
/** | |||
* Prints XML output of the test to a specified Writer. | |||
* | |||
* @author <a href="mailto:stefan.bodewig@megabit.net">Stefan Bodewig</a> | |||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||
*/ | |||
public class XMLJUnitResultFormatter implements JUnitResultFormatter { | |||
@@ -120,7 +121,7 @@ public class XMLJUnitResultFormatter implements JUnitResultFormatter { | |||
public void startTestSuite(JUnitTest suite) { | |||
doc = getDocumentBuilder().newDocument(); | |||
rootElement = doc.createElement("testsuite"); | |||
rootElement.setAttribute("name", xmlEscape(suite.getName())); | |||
rootElement.setAttribute("name", suite.getName()); | |||
} | |||
/** | |||
@@ -137,7 +138,7 @@ public class XMLJUnitResultFormatter implements JUnitResultFormatter { | |||
try { | |||
wri = new OutputStreamWriter(out); | |||
wri.write("<?xml version=\"1.0\"?>\n"); | |||
write(rootElement, wri, 0); | |||
(new DOMElementWriter()).write(rootElement, wri, 0, " "); | |||
wri.flush(); | |||
} catch(IOException exc) { | |||
throw new BuildException("Unable to write log file", exc); | |||
@@ -161,7 +162,7 @@ public class XMLJUnitResultFormatter implements JUnitResultFormatter { | |||
public void startTest(Test t) { | |||
lastTestStart = System.currentTimeMillis(); | |||
currentTest = doc.createElement("testcase"); | |||
currentTest.setAttribute("name", xmlEscape(((TestCase) t).name())); | |||
currentTest.setAttribute("name", ((TestCase) t).name()); | |||
rootElement.appendChild(currentTest); | |||
} | |||
@@ -208,9 +209,9 @@ public class XMLJUnitResultFormatter implements JUnitResultFormatter { | |||
String message = t.getMessage(); | |||
if (message != null && message.length() > 0) { | |||
nested.setAttribute("message", xmlEscape(t.getMessage())); | |||
nested.setAttribute("message", t.getMessage()); | |||
} | |||
nested.setAttribute("type", xmlEscape(t.getClass().getName())); | |||
nested.setAttribute("type", t.getClass().getName()); | |||
StringWriter swr = new StringWriter(); | |||
t.printStackTrace(new PrintWriter(swr, true)); | |||
@@ -218,97 +219,4 @@ public class XMLJUnitResultFormatter implements JUnitResultFormatter { | |||
nested.appendChild(trace); | |||
} | |||
/** | |||
* Translates <, & , " and > to corresponding entities. | |||
*/ | |||
private String xmlEscape(String orig) { | |||
if (orig == null) return ""; | |||
StringBuffer temp = new StringBuffer(); | |||
StringCharacterIterator sci = new StringCharacterIterator(orig); | |||
for (char c = sci.first(); c != CharacterIterator.DONE; | |||
c = sci.next()) { | |||
switch (c) { | |||
case '<': | |||
temp.append("<"); | |||
break; | |||
case '>': | |||
temp.append(">"); | |||
break; | |||
case '\"': | |||
temp.append("""); | |||
break; | |||
case '&': | |||
temp.append("&"); | |||
break; | |||
default: | |||
temp.append(c); | |||
break; | |||
} | |||
} | |||
return temp.toString(); | |||
} | |||
/** | |||
* Writes a DOM element to a stream. | |||
*/ | |||
private static void write(Element element, Writer out, int indent) throws IOException { | |||
// Write indent characters | |||
for (int i = 0; i < indent; i++) { | |||
out.write("\t"); | |||
} | |||
// 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(attr.getValue()); | |||
out.write("\""); | |||
} | |||
out.write(">"); | |||
// Write child attributes and text | |||
boolean hasChildren = false; | |||
NodeList children = element.getChildNodes(); | |||
for (int i = 0; i < children.getLength(); i++) { | |||
Node child = children.item(i); | |||
if (child.getNodeType() == Node.ELEMENT_NODE) { | |||
if (!hasChildren) { | |||
out.write("\n"); | |||
hasChildren = true; | |||
} | |||
write((Element)child, out, indent + 1); | |||
} | |||
if (child.getNodeType() == Node.TEXT_NODE) { | |||
out.write("<![CDATA["); | |||
out.write(((Text)child).getData()); | |||
out.write("]]>"); | |||
} | |||
} | |||
// 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("\t"); | |||
} | |||
} | |||
// Write element close | |||
out.write("</"); | |||
out.write(element.getTagName()); | |||
out.write(">\n"); | |||
} | |||
} // XMLJUnitResultFormatter |
@@ -0,0 +1,218 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000 The Apache Software Foundation. All rights | |||
* reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | |||
* are met: | |||
* | |||
* 1. Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* 2. Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in | |||
* the documentation and/or other materials provided with the | |||
* distribution. | |||
* | |||
* 3. The end-user documentation included with the redistribution, if | |||
* any, must include the following acknowlegement: | |||
* "This product includes software developed by the | |||
* Apache Software Foundation (http://www.apache.org/)." | |||
* Alternately, this acknowlegement may appear in the software itself, | |||
* if and wherever such third-party acknowlegements normally appear. | |||
* | |||
* 4. The names "The Jakarta Project", "Ant", and "Apache Software | |||
* Foundation" must not be used to endorse or promote products derived | |||
* from this software without prior written permission. For written | |||
* permission, please contact apache@apache.org. | |||
* | |||
* 5. Products derived from this software may not be called "Apache" | |||
* nor may "Apache" appear in their names without prior written | |||
* permission of the Apache Group. | |||
* | |||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | |||
* ==================================================================== | |||
* | |||
* This software consists of voluntary contributions made by many | |||
* individuals on behalf of the Apache Software Foundation. For more | |||
* information on the Apache Software Foundation, please see | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.util; | |||
import java.io.*; | |||
import org.w3c.dom.*; | |||
/** | |||
* Writes a DOM tree to a given Writer. | |||
* | |||
* <p>Utility class used by {@link org.apache.tools.ant.XmlLogger | |||
* XmlLogger} and | |||
* org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter | |||
* XMLJUnitResultFormatter}.</p> | |||
* | |||
* @author The original author of XmlLogger | |||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||
*/ | |||
public class DOMElementWriter { | |||
private static String lSep = System.getProperty("line.separator"); | |||
private StringBuffer sb = new StringBuffer(); | |||
/** | |||
* 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. | |||
* | |||
* @param element the Root DOM element of the tree | |||
* @param out where to send the output | |||
* @param indent number of | |||
* @param indentWith strings, | |||
* that should be used to indent the corresponding tag. | |||
*/ | |||
public void write(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(">"); | |||
// 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); | |||
if (child.getNodeType() == Node.ELEMENT_NODE) { | |||
if (!hasChildren) { | |||
out.write(lSep); | |||
hasChildren = true; | |||
} | |||
write((Element)child, out, indent + 1, indentWith); | |||
} | |||
if (child.getNodeType() == Node.TEXT_NODE) { | |||
out.write("<![CDATA["); | |||
out.write(((Text)child).getData()); | |||
out.write("]]>"); | |||
} | |||
} | |||
// 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(" "); | |||
} | |||
} | |||
// Write element close | |||
out.write("</"); | |||
out.write(element.getTagName()); | |||
out.write(">"); | |||
out.write(lSep); | |||
} | |||
/** | |||
* Escape <, & and " as their entities. | |||
*/ | |||
public String encode(String value) { | |||
sb.setLength(0); | |||
for (int i=0; i<value.length(); i++) { | |||
char c = value.charAt(i); | |||
switch (c) { | |||
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: | |||
sb.append(c); | |||
break; | |||
} | |||
} | |||
return sb.toString(); | |||
} | |||
/** | |||
* 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; | |||
} | |||
} |
@@ -0,0 +1,104 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000 The Apache Software Foundation. All rights | |||
* reserved. | |||
* | |||
* Redistribution and use in source and binary forms, with or without | |||
* modification, are permitted provided that the following conditions | |||
* are met: | |||
* | |||
* 1. Redistributions of source code must retain the above copyright | |||
* notice, this list of conditions and the following disclaimer. | |||
* | |||
* 2. Redistributions in binary form must reproduce the above copyright | |||
* notice, this list of conditions and the following disclaimer in | |||
* the documentation and/or other materials provided with the | |||
* distribution. | |||
* | |||
* 3. The end-user documentation included with the redistribution, if | |||
* any, must include the following acknowlegement: | |||
* "This product includes software developed by the | |||
* Apache Software Foundation (http://www.apache.org/)." | |||
* Alternately, this acknowlegement may appear in the software itself, | |||
* if and wherever such third-party acknowlegements normally appear. | |||
* | |||
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software | |||
* Foundation" must not be used to endorse or promote products derived | |||
* from this software without prior written permission. For written | |||
* permission, please contact apache@apache.org. | |||
* | |||
* 5. Products derived from this software may not be called "Apache" | |||
* nor may "Apache" appear in their names without prior written | |||
* permission of the Apache Group. | |||
* | |||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR | |||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
* SUCH DAMAGE. | |||
* ==================================================================== | |||
* | |||
* This software consists of voluntary contributions made by many | |||
* individuals on behalf of the Apache Software Foundation. For more | |||
* information on the Apache Software Foundation, please see | |||
* <http://www.apache.org/>. | |||
*/ | |||
package org.apache.tools.ant.util; | |||
import junit.framework.Test; | |||
import junit.framework.TestCase; | |||
import junit.framework.TestSuite; | |||
/** | |||
* Tests for org.apache.tools.ant.util.DOMElementWriter. | |||
* | |||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||
*/ | |||
public class DOMElementWriterTest extends TestCase { | |||
private DOMElementWriter w = new DOMElementWriter(); | |||
public DOMElementWriterTest(String name) { | |||
super(name); | |||
} | |||
public void testIsReference() { | |||
assert("", w.isReference("")); | |||
assert(" ", w.isReference(" ")); | |||
assert(" ", w.isReference(" ")); | |||
assert("&#A0;", !w.isReference("&#A0;")); | |||
assert("20;", !w.isReference("20;")); | |||
assert("", !w.isReference("")); | |||
assert(""", w.isReference(""")); | |||
assert("'", w.isReference("'")); | |||
assert(">", w.isReference(">")); | |||
assert("<", w.isReference("<")); | |||
assert("&", w.isReference("&")); | |||
} | |||
public void testEncode() { | |||
assertEquals("", w.encode("")); | |||
assertEquals(" ", w.encode(" ")); | |||
assertEquals(" ", w.encode(" ")); | |||
assertEquals("&#A0;", w.encode("&#A0;")); | |||
assertEquals("20;", w.encode("20;")); | |||
assertEquals("&#20", w.encode("")); | |||
assertEquals(""", w.encode(""")); | |||
assertEquals("'", w.encode("'")); | |||
assertEquals(">", w.encode(">")); | |||
assertEquals("<", w.encode("<")); | |||
assertEquals("&", w.encode("&")); | |||
assertEquals(""", w.encode("\"")); | |||
assertEquals("<", w.encode("<")); | |||
assertEquals("&", w.encode("&")); | |||
} | |||
} |