package net.sf.saxon;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.om.NamePool;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trace.InstructionInfo;
import net.sf.saxon.trace.InstructionInfoProvider;
import net.sf.saxon.trace.Location;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ValidationException;
import org.xml.sax.SAXException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMLocator;
import java.io.PrintStream;
import java.io.Serializable;
/**
* <B>StandardErrorListener</B> is the standard error handler for XSLT processing
* errors, used if no other ErrorListener is nominated.
* @author Michael H. Kay
*/
public class StandardErrorListener implements ErrorListener, Serializable {
private int recoveryPolicy = Configuration.RECOVER_WITH_WARNINGS;
private int warningCount = 0;
protected transient PrintStream errorOutput = System.err;
/**
* Create a Standard Error Listener
*/
public StandardErrorListener() {}
/**
* Make a clean copy of this ErrorListener. This is necessary because the
* standard error listener is stateful (it remembers how many errors there have been)
* @param hostLanguage the host language (not used by this implementation)
* @return a copy of this error listener
*/
public StandardErrorListener makeAnother(int hostLanguage) {
return new StandardErrorListener();
}
// Note, when the standard error listener is used, a new
// one is created for each transformation, because it holds
// the recovery policy and the warning count.
/**
* Set output destination for error messages (default is System.err)
* @param writer The PrintStream to use for error messages
*/
public void setErrorOutput(PrintStream writer) {
errorOutput = writer;
}
/**
* Get the error output stream
* @return the error output stream
*/
public PrintStream getErrorOutput() {
return errorOutput;
}
/**
* Set the recovery policy
* @param policy the recovery policy for XSLT recoverable errors. One of
* {@link Configuration#RECOVER_SILENTLY},
* {@link Configuration#RECOVER_WITH_WARNINGS},
* {@link Configuration#DO_NOT_RECOVER}.
*/
public void setRecoveryPolicy(int policy) {
recoveryPolicy = policy;
}
/**
* Get the recovery policy
* @return the recovery policy for XSLT recoverable errors. One of
* {@link Configuration#RECOVER_SILENTLY},
* {@link Configuration#RECOVER_WITH_WARNINGS},
* {@link Configuration#DO_NOT_RECOVER}.
*/
public int getRecoveryPolicy() {
return recoveryPolicy;
}
/**
* Receive notification of a warning.
*
* <p>Transformers can use this method to report conditions that
* are not errors or fatal errors. The default behaviour is to
* take no action.</p>
*
* <p>After invoking this method, the Transformer must continue with
* the transformation. It should still be possible for the
* application to process the document through to the end.</p>
*
* @param exception The warning information encapsulated in a
* transformer exception.
*
* @throws javax.xml.transform.TransformerException if the application
* chooses to discontinue the transformation.
*
* @see javax.xml.transform.TransformerException
*/
public void warning(TransformerException exception)
throws TransformerException {
if (recoveryPolicy==Configuration.RECOVER_SILENTLY) {
// do nothing
return;
}
if (errorOutput == null) {
// can happen after deserialization
errorOutput = System.err;
}
String message = "";
if (exception.getLocator()!=null) {
message = getLocationMessage(exception) + "\n ";
}
message += wordWrap(getExpandedMessage(exception));
if (exception instanceof ValidationException) {
errorOutput.println("Validation error " + message);
} else {
errorOutput.println("Warning: " + message);
warningCount++;
if (warningCount > 25) {
errorOutput.println("No more warnings will be displayed");
recoveryPolicy = Configuration.RECOVER_SILENTLY;
warningCount = 0;
}
}
}
/**
* Receive notification of a recoverable error.
*
* <p>The transformer must continue to provide normal parsing events
* after invoking this method. It should still be possible for the
* application to process the document through to the end.</p>
*
* <p>The action of the standard error listener depends on the
* recovery policy that has been set, which may be one of RECOVER_SILENTLY,
* RECOVER_WITH_WARNING, or DO_NOT_RECOVER
*
* @param exception The error information encapsulated in a
* transformer exception.
*
* @throws TransformerException if the application
* chooses to discontinue the transformation.
*
* @see TransformerException
*/
public void error(TransformerException exception) throws TransformerException {
if (recoveryPolicy==Configuration.RECOVER_SILENTLY) {
// do nothing
return;
}
if (errorOutput == null) {
// can happen after deserialization
errorOutput = System.err;
}
String message;
if (exception instanceof ValidationException) {
String explanation = getExpandedMessage(exception);
String constraintReference = ((ValidationException)exception).getConstraintReferenceMessage();
if (constraintReference != null) {
explanation += " (" + constraintReference + ')';
}
message = "Validation error " +
getLocationMessage(exception) +
"\n " +
wordWrap(explanation);
} else {
String prefix = (recoveryPolicy==Configuration.RECOVER_WITH_WARNINGS ?
"Recoverable error " : "Error ");
message = prefix + getLocationMessage(exception) +
"\n " +
wordWrap(getExpandedMessage(exception));
}
if (exception instanceof ValidationException) {
errorOutput.println(message);
} else if (recoveryPolicy==Configuration.RECOVER_WITH_WARNINGS) {
errorOutput.println(message);
warningCount++;
if (warningCount > 25) {
errorOutput.println("No more warnings will be displayed");
recoveryPolicy = Configuration.RECOVER_SILENTLY;
warningCount = 0;
}
} else {
errorOutput.println(message);
errorOutput.println("Processing terminated because error recovery is disabled");
throw XPathException.makeXPathException(exception);
}
}
/**
* Receive notification of a non-recoverable error.
*
* <p>The application must assume that the transformation cannot
* continue after the Transformer has invoked this method,
* and should continue (if at all) only to collect
* addition error messages. In fact, Transformers are free
* to stop reporting events once this method has been invoked.</p>
*
* @param exception The error information encapsulated in a
* transformer exception.
*
* @throws TransformerException if the application
* chooses to discontinue the transformation.
*
* @see TransformerException
*/
public void fatalError(TransformerException exception) throws TransformerException {
if (exception instanceof XPathException && ((XPathException)exception).hasBeenReported()) {
// don't report the same error twice
return;
}
if (errorOutput == null) {
// can happen after deserialization
errorOutput = System.err;
}
String message;
if (exception instanceof ValidationException) {
String explanation = getExpandedMessage(exception);
String constraintReference = ((ValidationException)exception).getConstraintReferenceMessage();
if (constraintReference != null) {
explanation += " (" + constraintReference + ')';
}
message = "Validation error " +
getLocationMessage(exception) +
"\n " +
wordWrap(explanation);
} else {
message = "Error " +
getLocationMessage(exception) +
"\n " +
wordWrap(getExpandedMessage(exception));
}
errorOutput.println(message);
if (exception instanceof XPathException) {
((XPathException)exception).setHasBeenReported();
// probably redundant. It's the caller's job to set this flag, because there might be
// a non-standard error listener in use.
}
}
/**
* Get a string identifying the location of an error.
* @param err the exception containing the location information
* @return a message string describing the location
*/
public static String getLocationMessage(TransformerException err) {
SourceLocator loc = err.getLocator();
while (loc == null) {
if (err.getException() instanceof TransformerException) {
err = (TransformerException)err.getException();
loc = err.getLocator();
} else if (err.getCause() instanceof TransformerException) {
err = (TransformerException)err.getCause();
loc = err.getLocator();
} else {
return "";
}
}
XPathContext context = null;
if (err instanceof XPathException) {
context = ((XPathException)err).getXPathContext();
}
return getLocationMessage(loc, context);
}
private static String getLocationMessage(SourceLocator loc, XPathContext context) {
String locmessage = "";
String systemId = null;
int lineNumber = -1;
if (loc instanceof DOMLocator) {
locmessage += "at " + ((DOMLocator)loc).getOriginatingNode().getNodeName() + ' ';
} else if (loc instanceof NodeInfo) {
locmessage += "at " + ((NodeInfo)loc).getDisplayName() + ' ';
} else if (loc instanceof InstructionInfoProvider) {
String instructionName = getInstructionName(((InstructionInfoProvider)loc), context);
if (!"".equals(instructionName)) {
locmessage += "at " + instructionName + ' ';
}
systemId = ((InstructionInfoProvider)loc).getInstructionInfo().getSystemId();
lineNumber = ((InstructionInfoProvider)loc).getInstructionInfo().getLineNumber();
}
if (lineNumber == -1) {
lineNumber = loc.getLineNumber();
}
if (lineNumber != -1) {
locmessage += "on line " + lineNumber + ' ';
}
if (loc.getColumnNumber() != -1) {
locmessage += "column " + loc.getColumnNumber() + ' ';
}
if (systemId != null && systemId.length() == 0) {
systemId = null;
}
if (systemId == null) {
systemId = loc.getSystemId();
}
if (systemId != null && systemId.length() != 0) {
locmessage += "of " + systemId + ':';
}
return locmessage;
}
/**
* Get a string containing the message for this exception and all contained exceptions
* @param err the exception containing the required information
* @return a message that concatenates the message of this exception with its contained exceptions,
* also including information about the error code and location.
*/
public static String getExpandedMessage(TransformerException err) {
String code = null;
if (err instanceof XPathException) {
code = ((XPathException)err).getErrorCodeLocalPart();
}
if (code == null && err.getException() instanceof XPathException) {
code = ((XPathException)err.getException()).getErrorCodeLocalPart();
}
String message = "";
if (code != null) {
message = code;
}
Throwable e = err;
while (true) {
if (e == null) {
break;
}
String next = e.getMessage();
if (next==null) next="";
if (next.startsWith("net.sf.saxon.trans.XPathException: ")) {
next = next.substring(next.indexOf(": ") + 2);
}
if (!("TRaX Transform Exception".equals(next) || message.endsWith(next))) {
if (!"".equals(message) && !message.trim().endsWith(":")) {
message += ": ";
}
message += next;
}
if (e instanceof TransformerException) {
e = ((TransformerException)e).getException();
} else if (e instanceof SAXException) {
e = ((SAXException)e).getException();
} else {
// e.printStackTrace();
break;
}
}
return message;
}
/**
* Extract a name identifying the instruction at which an error occurred
* @param inst the provider of information
* @param context the dynamic evaluation context
* @return the name of the containing instruction or expression, in user-meaningful terms
*/
private static String getInstructionName(InstructionInfoProvider inst, XPathContext context) {
// TODO: subclass this for XSLT and XQuery
if (context==null) {
return "";
}
NamePool pool = context.getNamePool();
try {
InstructionInfo info = inst.getInstructionInfo();
int construct = info.getConstructType();
if (construct < 1024 &&
construct != StandardNames.XSL_FUNCTION &&
construct != StandardNames.XSL_TEMPLATE) {
// it's a standard name
if (context.getController().getExecutable().getHostLanguage() == Configuration.XSLT) {
return StandardNames.getDisplayName(construct);
} else {
String s = StandardNames.getDisplayName(construct);
int colon = s.indexOf(':');
if (colon > 0) {
String local = s.substring(colon+1);
if (local.equals("document")) {
return "document node constructor";
} else if (local.equals("text") || s.equals("value-of")) {
return "text node constructor";
} else if (local.equals("element")) {
return "computed element constructor";
} else if (local.equals("attribute")) {
return "computed attribute constructor";
} else if (local.equals("variable")) {
return "variable declaration";
} else if (local.equals("param")) {
return "external variable declaration";
}
}
return s;
}
}
switch (construct) {
case Location.LITERAL_RESULT_ELEMENT: {
StructuredQName qName = info.getObjectName(pool);
String name = "element constructor";
if (context != null) {
name += " <" + qName.getDisplayName() + '>';
}
return name;
}
case Location.LITERAL_RESULT_ATTRIBUTE: {
StructuredQName qName = info.getObjectName(pool);
String name = "attribute constructor";
if (context != null) {
name += ' ' + qName.getDisplayName() + "=\"{...}\"";
}
return name;
}
case StandardNames.XSL_FUNCTION: {
StructuredQName qName = info.getObjectName(pool);
String name = "function";
if (context != null) {
name += ' ' + qName.getDisplayName() + "()";
}
return name;
}
case StandardNames.XSL_TEMPLATE: {
StructuredQName qName = info.getObjectName(pool);
String name = "template";
if (context != null && qName != null) {
name += " name=\"" + qName.getDisplayName() + '\"';
}
return name;
}
default:
return "";
}
} catch (Exception err) {
return "";
}
}
/**
* Wordwrap an error message into lines of 72 characters or less (if possible)
* @param message the message to be word-wrapped
* @return the message after applying word-wrapping
*/
private static String wordWrap(String message) {
int nl = message.indexOf('\n');
if (nl < 0) {
nl = message.length();
}
if (nl > 100) {
int i = 90;
while (message.charAt(i) != ' ' && i>0) {
i--;
}
if (i>10) {
return message.substring(0, i) + "\n " + wordWrap(message.substring(i+1));
} else {
return message;
}
} else if (nl < message.length()) {
return message.substring(0, nl) + '\n' + wordWrap(message.substring(nl+1));
} else {
return message;
}
}
}
// The contents of this file are subject to the Mozilla Public License Version 1.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.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: all this file.
//
// The Initial Developer of the Original Code is Michael H. Kay.
//
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
//
// Contributor(s):
// Portions marked "e.g." are from Edwin Glaser (edwin@pannenleiter.de)
//