/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ /* * $Id$ */ /* * * XMLFileLogger.java * */ package org.apache.qetest; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; /** * Logger that saves output to a simple XML-format file. * @todo improve escapeString so it's more rigorous about escaping * @author Shane_Curcuru@lotus.com * @version $Id$ */ public class XMLFileLogger implements Logger { //----------------------------------------------------- //-------- Constants for results file structure -------- //----------------------------------------------------- /** XML root element tag: root of our output document. */ public static final String ELEM_RESULTSFILE = "resultsfile"; /** XML element tag: testFileInit() parent element. */ public static final String ELEM_TESTFILE = "testfile"; /** XML element tag: testFileClose() leaf element * emitted immediately before closing ELEM_TESTFILE. */ public static final String ELEM_FILERESULT = "fileresult"; /** XML element tag: testCaseInit() parent element. */ public static final String ELEM_TESTCASE = "testcase"; /** XML element tag: testCaseClose() leaf element * emitted immediately before closing ELEM_TESTCASE. */ public static final String ELEM_CASERESULT = "caseresult"; /** XML element tag: check*() element. */ public static final String ELEM_CHECKRESULT = "checkresult"; /** XML element tag: logStatistic() element. */ public static final String ELEM_STATISTIC = "statistic"; /** XML element tag: logStatistic() child element. */ public static final String ELEM_LONGVAL = "longval"; /** XML element tag: logStatistic() child element. */ public static final String ELEM_DOUBLEVAL = "doubleval"; /** XML element tag: log*Msg() element. */ public static final String ELEM_MESSAGE = "message"; /** XML element tag: logArbitrary() element. */ public static final String ELEM_ARBITRARY = "arbitrary"; /** XML element tag: logHashtable() parent element. */ public static final String ELEM_HASHTABLE = "hashtable"; /** XML element tag: logHashtable() child element. */ public static final String ELEM_HASHITEM = "hashitem"; /** XML attribute tag: log*Msg(), etc. attribute denoting * loggingLevel for that element. */ public static final String ATTR_LEVEL = "level"; /** XML attribute tag: comment value for various methods. */ public static final String ATTR_DESC = "desc"; /** XML attribute tag: Date.toString() attribute on * ELEM_TESTFILE and ELEM_FILERESULT. */ public static final String ATTR_TIME = "time"; /** XML attribute tag: PASS|FAIL|etc. attribute on * ELEM_CHECKRESULT, ELEM_CASERESULT, ELEM_FILERESULT. */ public static final String ATTR_RESULT = "result"; /** XML attribute tag: key name for ELEM_HASHITEM. */ public static final String ATTR_KEY = "key"; /** XML attribute tag: actual output filename on ELEM_RESULTSFILE. */ public static final String ATTR_FILENAME = OPT_LOGFILE; /** XML attribute tag: test filename on ELEM_TESTFILE. */ public static final String ATTR_TESTFILENAME = "filename"; /** Parameter: if we should flush on every testCaseClose(). */ public static final String OPT_FLUSHONCASECLOSE = "flushOnCaseClose"; //----------------------------------------------------- //-------- Class members and accessors -------- //----------------------------------------------------- /** If we're ready to start outputting yet. */ protected boolean ready = false; /** If an error has occoured in this Logger. */ protected boolean error = false; /** If we should flush on every testCaseClose(). */ protected boolean flushOnCaseClose = true; /** * Accessor for flushing; is set from properties. * * @return current flushing status */ public boolean getFlushOnCaseClose() { return (flushOnCaseClose); } /** * Accessor for flushing; is set from properties. * * @param b If we should flush on every testCaseClose() */ public void setFlushOnCaseClose(boolean b) { flushOnCaseClose = b; } /** If we have output anything yet. */ protected boolean anyOutput = false; /** Name of the file we're outputing to. */ protected String fileName = null; /** File reference and other internal convenience variables. */ protected File reportFile; /** File reference and other internal convenience variables. */ protected FileWriter reportWriter; /** File reference and other internal convenience variables. */ protected PrintWriter reportPrinter; /** Generic properties for this logger; sort-of replaces instance variables. */ protected Properties loggerProps = null; //----------------------------------------------------- //-------- Control and utility routines -------- //----------------------------------------------------- /** Simple constructor, does not perform initialization. */ public XMLFileLogger() { /* no-op */ } /** * Constructor calls initialize(p). * @param p Properties block to initialize us with. */ public XMLFileLogger(Properties p) { ready = initialize(p); } /** * Return a description of what this Logger does. * @return "reports results in XML to specified fileName". */ public String getDescription() { return ("org.apache.qetest.XMLFileLogger - reports results in XML to specified fileName."); } /** * Returns information about the Property name=value pairs * that are understood by this Logger: fileName=filename. * @return same as {@link java.applet.Applet.getParameterInfo}. */ public String[][] getParameterInfo() { String pinfo[][] = { { OPT_LOGFILE, "String", "Name of file to use for output; required" }, { OPT_FLUSHONCASECLOSE, "boolean", "if we should flush on every testCaseClose(); optional; default:true" } }; return pinfo; } /** * Accessor methods for our properties block. * * @return our current properties; may be null */ public Properties getProperties() { return loggerProps; } /** * Accessor methods for our properties block. * @param p Properties to set (is cloned). */ public void setProperties(Properties p) { if (p != null) { loggerProps = (Properties) p.clone(); } } /** * Initialize this XMLFileLogger. * Must be called before attempting to log anything. * Opens a FileWriter for our output, and logs Record format: *
<resultfile fileName="name of result file">* * If no name provided, supplies a default one in current dir. * * @param p Properties block to initialize from * @return true if we think we initialized OK */ public boolean initialize(Properties p) { setProperties(p); fileName = loggerProps.getProperty(OPT_LOGFILE, fileName); if ((fileName == null) || fileName.equals("")) { // Make up a file name fileName = "XMLFileLogger-default-results.xml"; loggerProps.put(OPT_LOGFILE, fileName); } // Create a file and ensure it has a place to live; be sure // to insist on an absolute path for later parent path creation reportFile = new File(fileName); try { reportFile = new File(reportFile.getCanonicalPath()); } catch (IOException ioe1) { reportFile = new File(reportFile.getAbsolutePath()); } // Note: bare filenames may not have parents, so catch and ignore exceptions try { File parent = new File(reportFile.getParent()); if ((!parent.mkdirs()) && (!parent.exists())) { // Couldn't create or find the directory for the file to live in, so bail error = true; ready = false; System.err.println( "XMLFileLogger.initialize() WARNING: cannot create directories: " + fileName); // Don't return yet: see if the reportWriter can still create the file later // return(false); } } catch (Exception e) { // No-op: ignore if the parent's not there; trust that the file will get created later } try { reportWriter = new FileWriter(reportFile); } catch (IOException e) { System.err.println("XMLFileLogger.initialize() EXCEPTION: " + e.toString()); e.printStackTrace(); error = true; ready = false; return false; } String tmp = loggerProps.getProperty(OPT_FLUSHONCASECLOSE); if (null != tmp) { setFlushOnCaseClose((Boolean.valueOf(tmp)).booleanValue()); } // Use BufferedWriter for better general performance reportPrinter = new PrintWriter(new BufferedWriter(reportWriter)); ready = true; return startResultsFile(); } /** * Is this Logger ready to log results? * @return status - true if it's ready to report, false otherwise */ public boolean isReady() { // Ensure our underlying logger, if one, is still OK if ((reportPrinter != null) && reportPrinter.checkError()) { // NEEDSWORK: should we set ready = false in this case? // errors in the PrintStream are not necessarily fatal error = true; ready = false; } return ready; } /** Flush this logger - ensure our File is flushed. */ public void flush() { if (isReady()) { reportPrinter.flush(); } } /** * Close this logger - ensure our File, etc. are closed. * Record format: *
</resultfile>*/ public void close() { flush(); if (isReady()) { closeResultsFile(); reportPrinter.close(); } ready = false; } /** * worker method to dump the xml header and open the resultsfile element. * * @return true if ready/OK, false if not ready (meaning we may * not have output anything!) */ protected boolean startResultsFile() { if (isReady()) { // Write out XML header and root test result element reportPrinter.println(""); // Note: this tag is closed in our .close() method, which the caller had better call! reportPrinter.println("<" + ELEM_RESULTSFILE + " " + ATTR_FILENAME + "=\"" + fileName + "\">"); return true; } else return false; } /** * worker method to close the resultsfile element. * * @return true if ready/OK, false if not ready (meaning we may * not have output a closing tag!) */ protected boolean closeResultsFile() { if (isReady()) { reportPrinter.println("" + ELEM_RESULTSFILE + ">"); return true; } else return false; } //----------------------------------------------------- //-------- Testfile / Testcase start and stop routines -------- //----------------------------------------------------- /** * Report that a testfile has started. * Begins a testfile element. Record format: *
<testfile desc="test description" time="timestamp">* @param name file name or tag specifying the test. * @param comment comment about the test. */ public void testFileInit(String name, String comment) { if (isReady()) { reportPrinter.println("<" + ELEM_TESTFILE + " " + ATTR_TESTFILENAME + "=\"" + escapeString(name) + "\" " + ATTR_DESC + "=\"" + escapeString(comment) + "\" " + ATTR_TIME + "=\"" + (new Date()).toString() + "\">"); } } /** * Report that a testfile has finished, and report it's result; flushes output. * Ends a testfile element. Record format: *
<fileresult desc="test description" result="pass/fail status" time="timestamp">
* </testfile>
* @param msg message to log out
* @param result result of testfile
*/
public void testFileClose(String msg, String result)
{
if (isReady())
{
reportPrinter.println("<" + ELEM_FILERESULT + " " + ATTR_DESC
+ "=\"" + escapeString(msg) + "\" "
+ ATTR_RESULT + "=\"" + result + "\" "
+ ATTR_TIME + "=\""
+ (new Date()).toString() + "\"/>");
reportPrinter.println("" + ELEM_TESTFILE + ">");
}
flush();
}
/** Optimization: for heavy use methods, form pre-defined constants to save on string concatenation. */
private static final String TESTCASEINIT_HDR = "<" + ELEM_TESTCASE + " "
+ ATTR_DESC + "=\"";
/**
* Report that a testcase has begun.
* Begins a testcase element. Record format:
* <testcase desc="case description">* * @param comment message to log out */ public void testCaseInit(String comment) { if (isReady()) { reportPrinter.println(TESTCASEINIT_HDR + escapeString(comment) + "\">"); } } /** NEEDSDOC Field TESTCASECLOSE_HDR */ private static final String TESTCASECLOSE_HDR = "<" + ELEM_CASERESULT + " " + ATTR_DESC + "=\""; /** * Report that a testcase has finished, and report it's result. * Optionally flushes output. Ends a testcase element. Record format: *
<caseresult desc="case description" result="pass/fail status">
* </testcase>
* @param msg message of name of test case to log out
* @param result result of testfile
*/
public void testCaseClose(String msg, String result)
{
if (isReady())
{
reportPrinter.println(TESTCASECLOSE_HDR + escapeString(msg)
+ "\" " + ATTR_RESULT + "=\"" + result
+ "\"/>");
reportPrinter.println("" + ELEM_TESTCASE + ">");
}
if (getFlushOnCaseClose())
flush();
}
//-----------------------------------------------------
//-------- Test results logging routines --------
//-----------------------------------------------------
/** NEEDSDOC Field MESSAGE_HDR */
private static final String MESSAGE_HDR = "<" + ELEM_MESSAGE + " "
+ ATTR_LEVEL + "=\"";
/**
* Report a comment to result file with specified severity.
* Record format: <message level="##">msg</message>* @param level severity or class of message. * @param msg comment to log out. */ public void logMsg(int level, String msg) { if (isReady()) { reportPrinter.print(MESSAGE_HDR + level + "\">"); reportPrinter.print(escapeString(msg)); reportPrinter.println("" + ELEM_MESSAGE + ">"); } } /** NEEDSDOC Field ARBITRARY_HDR */ private static final String ARBITRARY_HDR = "<" + ELEM_ARBITRARY + " " + ATTR_LEVEL + "=\""; /** * Report an arbitrary String to result file with specified severity. * Appends and prepends \\n newline characters at the start and * end of the message to separate it from the tags. * Record format:
<arbitrary level="##"><![CDATA[
* msg
* ]]></arbitrary>
*
* Note that arbitrary messages are always wrapped in CDATA
* sections to ensure that any non-valid XML is wrapped. This needs
* to be investigated for other elements as well (i.e. we should set a
* standard for what Logger calls must be well-formed or not).
* @param level severity or class of message.
* @param msg arbitrary String to log out.
* @todo investigate not fully escaping this string, since
* it does get wrappered in CDATA
*/
public void logArbitrary(int level, String msg)
{
if (isReady())
{
reportPrinter.println(ARBITRARY_HDR + level + "\">" + ELEM_ARBITRARY + ">");
}
}
/** NEEDSDOC Field STATISTIC_HDR */
private static final String STATISTIC_HDR = "<" + ELEM_STATISTIC + " "
+ ATTR_LEVEL + "=\"";
/**
* Logs out statistics to result file with specified severity.
* Record format: <statistic level="##" desc="msg"><longval>1234</longval><doubleval>1.234</doubleval></statistic>* @param level severity of message. * @param lVal statistic in long format. * @param dVal statistic in double format. * @param msg comment to log out. */ public void logStatistic(int level, long lVal, double dVal, String msg) { if (isReady()) { reportPrinter.print(STATISTIC_HDR + level + "\" " + ATTR_DESC + "=\"" + escapeString(msg) + "\">"); reportPrinter.print("<" + ELEM_LONGVAL + ">" + lVal + "" + ELEM_LONGVAL + ">"); reportPrinter.print("<" + ELEM_DOUBLEVAL + ">" + dVal + "" + ELEM_DOUBLEVAL + ">"); reportPrinter.println("" + ELEM_STATISTIC + ">"); } } /** * Logs out Throwable.toString() and a stack trace of the * Throwable with the specified severity. *
This uses logArbitrary to log out your msg - message, * a newline, throwable.toString(), a newline, * and then throwable.printStackTrace().
* //@todo future work to use logElement() to output * a specific <throwable> element instead. * @author Shane_Curcuru@lotus.com * @param level severity of message. * @param throwable throwable/exception to log out. * @param msg description of the throwable. */ public void logThrowable(int level, Throwable throwable, String msg) { if (isReady()) { StringWriter sWriter = new StringWriter(); sWriter.write(msg + "\n"); sWriter.write(throwable.toString() + "\n"); PrintWriter pWriter = new PrintWriter(sWriter); throwable.printStackTrace(pWriter); logArbitrary(level, sWriter.toString()); } } /** * Logs out a element to results with specified severity. * Uses user-supplied element name and attribute list. Currently * attribute values and msg are forced .toString(). Also, * 'level' is forced to be the first attribute of the element. * Record format: *<element_text level="##"
* attribute1="value1"
* attribute2="value2"
* attributen="valuen">
* msg
* </element_text>
* @author Shane_Curcuru@lotus.com
* @param level severity of message.
* @param element name of enclosing element
* @param attrs hash of name=value attributes; note that the
* caller must ensure they're legal XML
* @param msg Object to log out .toString(); caller should
* ensure it's legal XML (no CDATA is supplied)
*/
public void logElement(int level, String element, Hashtable attrs,
Object msg)
{
if (isReady()
&& (element != null)
&& (attrs != null)
)
{
reportPrinter.println("<" + element + " " + ATTR_LEVEL + "=\""
+ level + "\"");
for (Enumeration keys = attrs.keys();
keys.hasMoreElements(); /* no increment portion */ )
{
Object key = keys.nextElement();
reportPrinter.println(key.toString() + "=\""
+ escapeString(attrs.get(key).toString()) + "\"");
}
reportPrinter.println(">");
if (msg != null)
reportPrinter.println(msg.toString());
reportPrinter.println("" + element + ">");
}
}
/** NEEDSDOC Field HASHTABLE_HDR */
private static final String HASHTABLE_HDR = "<" + ELEM_HASHTABLE + " "
+ ATTR_LEVEL + "=\"";
// Note the HASHITEM_HDR indent; must be updated if we ever switch to another indenting method.
/** NEEDSDOC Field HASHITEM_HDR */
private static final String HASHITEM_HDR = " <" + ELEM_HASHITEM + " "
+ ATTR_KEY + "=\"";
/**
* Logs out contents of a Hashtable with specified severity.
* Indents each hashitem within the table.
* Record format: <hashtable level="##" desc="msg"/>
* <hashitem key="key1">value1</hashitem>
* <hashitem key="key2">value2</hashitem>
* </hashtable>
*
* @param level severity or class of message.
* @param hash Hashtable to log the contents of.
* @param msg decription of the Hashtable.
*/
public void logHashtable(int level, Hashtable hash, String msg)
{
if (isReady())
{
reportPrinter.println(HASHTABLE_HDR + level + "\" " + ATTR_DESC
+ "=\"" + escapeString(msg) + "\">");
if (hash == null)
{
reportPrinter.print("<" + ELEM_HASHITEM + " " + ATTR_KEY
+ "=\"null\">");
reportPrinter.println("" + ELEM_HASHITEM + ">");
}
try
{
for (Enumeration keys = hash.keys();
keys.hasMoreElements(); /* no increment portion */ )
{
Object key = keys.nextElement();
// Ensure we'll have clean output by pre-fetching value before outputting anything
String value = escapeString(hash.get(key).toString());
reportPrinter.print(HASHITEM_HDR + escapeString(key.toString())
+ "\">");
reportPrinter.print(value);
reportPrinter.println("" + ELEM_HASHITEM + ">");
}
}
catch (Exception e)
{
// No-op: should ensure we have clean output
}
reportPrinter.println("" + ELEM_HASHTABLE + ">");
}
}
//-----------------------------------------------------
//-------- Test results reporting check* routines --------
//-----------------------------------------------------
/** NEEDSDOC Field CHECKPASS_HDR */
private static final String CHECKPASS_HDR = "<" + ELEM_CHECKRESULT + " "
+ ATTR_RESULT + "=\""
+ Reporter.PASS + "\" "
+ ATTR_DESC + "=\"";
/**
* Writes out a Pass record with comment.
* Record format: <checkresult result="PASS" desc="comment"/>* @param comment comment to log with the pass record. */ public void checkPass(String comment) { if (isReady()) { reportPrinter.println(CHECKPASS_HDR + escapeString(comment) + "\"/>"); } } /** NEEDSDOC Field CHECKAMBG_HDR */ private static final String CHECKAMBG_HDR = "<" + ELEM_CHECKRESULT + " " + ATTR_RESULT + "=\"" + Reporter.AMBG + "\" " + ATTR_DESC + "=\""; /** * Writes out an ambiguous record with comment. * Record format:
<checkresult result="AMBG" desc="comment"/>* @param comment comment to log with the ambg record. */ public void checkAmbiguous(String comment) { if (isReady()) { reportPrinter.println(CHECKAMBG_HDR + escapeString(comment) + "\"/>"); } } /** NEEDSDOC Field CHECKFAIL_HDR */ private static final String CHECKFAIL_HDR = "<" + ELEM_CHECKRESULT + " " + ATTR_RESULT + "=\"" + Reporter.FAIL + "\" " + ATTR_DESC + "=\""; /** * Writes out a Fail record with comment. * Record format:
<checkresult result="FAIL" desc="comment"/>* @param comment comment to log with the fail record. */ public void checkFail(String comment) { if (isReady()) { reportPrinter.println(CHECKFAIL_HDR + escapeString(comment) + "\"/>"); } } /** NEEDSDOC Field CHECKERRR_HDR */ private static final String CHECKERRR_HDR = "<" + ELEM_CHECKRESULT + " " + ATTR_RESULT + "=\"" + Reporter.ERRR + "\" " + ATTR_DESC + "=\""; /** * Writes out a Error record with comment. * Record format:
<checkresult result="ERRR" desc="comment"/>* @param comment comment to log with the error record. */ public void checkErr(String comment) { if (isReady()) { reportPrinter.println(CHECKERRR_HDR + escapeString(comment) + "\"/>"); } } /* EXPERIMENTAL: have duplicate set of check*() methods that all output some form of ID as well as comment. Leave the non-ID taking forms for both simplicity to the end user who doesn't care about IDs as well as for backwards compatibility. */ /** ID_ATTR optimization for extra ID attribute. */ private static final String ATTR_ID = "\" id=\""; /** * Writes out a Pass record with comment. * Record format:
<checkresult result="PASS" desc="comment"/>* @param comment comment to log with the pass record. * @param ID token to log with the pass record. */ public void checkPass(String comment, String id) { if (isReady()) { StringBuffer tmp = new StringBuffer(CHECKPASS_HDR + escapeString(comment)); if (id != null) tmp.append(ATTR_ID + escapeString(id)); tmp.append("\"/>"); reportPrinter.println(tmp); } } /** * Writes out an ambiguous record with comment. * Record format:
<checkresult result="AMBG" desc="comment"/>* @param comment comment to log with the ambg record. * @param ID token to log with the pass record. */ public void checkAmbiguous(String comment, String id) { if (isReady()) { StringBuffer tmp = new StringBuffer(CHECKAMBG_HDR + escapeString(comment)); if (id != null) tmp.append(ATTR_ID + escapeString(id)); tmp.append("\"/>"); reportPrinter.println(tmp); } } /** * Writes out a Fail record with comment. * Record format:
<checkresult result="FAIL" desc="comment"/>* @param comment comment to log with the fail record. * @param ID token to log with the pass record. */ public void checkFail(String comment, String id) { if (isReady()) { StringBuffer tmp = new StringBuffer(CHECKFAIL_HDR + escapeString(comment)); if (id != null) tmp.append(ATTR_ID + escapeString(id)); tmp.append("\"/>"); reportPrinter.println(tmp); } } /** * Writes out a Error record with comment. * Record format:
<checkresult result="ERRR" desc="comment"/>* @param comment comment to log with the error record. * @param ID token to log with the pass record. */ public void checkErr(String comment, String id) { if (isReady()) { StringBuffer tmp = new StringBuffer(CHECKERRR_HDR + escapeString(comment)); if (id != null) tmp.append(ATTR_ID + escapeString(id)); tmp.append("\"/>"); reportPrinter.println(tmp); } } //----------------------------------------------------- //-------- Worker routines for XML string escaping -------- //----------------------------------------------------- /** * Lifted from org.apache.xml.serialize.transition.XMLSerializer * * @param ch character to get entity ref for * @return String of entity name */ public static String getEntityRef(char ch) { // Encode special XML characters into the equivalent character references. // These five are defined by default for all XML documents. switch (ch) { case '<' : return "lt"; case '>' : return "gt"; case '"' : return "quot"; case '\'' : return "apos"; case '&' : return "amp"; } return null; } /** * Identifies the last printable character in the Unicode range * that is supported by the encoding used with this serializer. * For 8-bit encodings this will be either 0x7E or 0xFF. * For 16-bit encodings this will be 0xFFFF. Characters that are * not printable will be escaped using character references. * Lifted from org.apache.xml.serialize.transition.BaseMarkupSerializer */ public static int _lastPrintable = 0x7E; /** * Lifted from org.apache.xml.serialize.transition.BaseMarkupSerializer * * @param ch character to escape * @return String that is escaped */ public static String printEscaped(char ch) { String charRef; // If there is a suitable entity reference for this // character, print it. The list of available entity // references is almost but not identical between // XML and HTML. charRef = getEntityRef(ch); if (charRef != null) { //_printer.printText( '&' ); // SC note we need to return a String for //_printer.printText( charRef ); // someone else to serialize //_printer.printText( ';' ); return "&" + charRef + ";"; } else if ((ch >= ' ' && ch <= _lastPrintable && ch != 0xF7) || ch == '\n' || ch == '\r' || ch == '\t') { // If the character is not printable, print as character reference. // Non printables are below ASCII space but not tab or line // terminator, ASCII delete, or above a certain Unicode threshold. //_printer.printText( ch ); return String.valueOf(ch); } else { //_printer.printText( "" ); //_printer.printText( Integer.toString( ch ) ); //_printer.printText( ';' ); return "" + Integer.toString(ch) + ";"; } } /** * Escapes a string so it may be printed as text content or attribute * value. Non printable characters are escaped using character references. * Where the format specifies a deault entity reference, that reference * is used (e.g. <). * Lifted from org.apache.xml.serialize.transition.BaseMarkupSerializer * * @param source The string to escape * @return String after escaping - needed for our application */ public static String escapeString(String source) { // Check for null; just return null (callers shouldn't care) if (source == null) { return null; } StringBuffer sb = new StringBuffer(); final int n = source.length(); for (int i = 0; i < n; ++i) { //char c = source.charAt( i ); sb.append(printEscaped(source.charAt(i))); } return sb.toString(); } } // end of class XMLFileLogger