001: package net.sf.saxon;
002:
003: import net.sf.saxon.expr.XPathContext;
004: import net.sf.saxon.om.NodeInfo;
005: import net.sf.saxon.style.StandardNames;
006: import net.sf.saxon.trace.InstructionInfo;
007: import net.sf.saxon.trace.InstructionInfoProvider;
008: import net.sf.saxon.trace.Location;
009: import net.sf.saxon.trans.DynamicError;
010: import net.sf.saxon.trans.XPathException;
011: import net.sf.saxon.type.ValidationException;
012: import org.xml.sax.SAXException;
013:
014: import javax.xml.transform.ErrorListener;
015: import javax.xml.transform.SourceLocator;
016: import javax.xml.transform.TransformerException;
017: import javax.xml.transform.dom.DOMLocator;
018: import java.io.PrintStream;
019:
020: /**
021: * <B>StandardErrorListener</B> is the standard error handler for XSLT processing
022: * errors, used if no other ErrorListener is nominated.
023: * @author Michael H. Kay
024: */
025:
026: public class StandardErrorListener implements ErrorListener {
027:
028: private int recoveryPolicy = Configuration.RECOVER_WITH_WARNINGS;
029: private int warningCount = 0;
030: protected transient PrintStream errorOutput = System.err;
031:
032: public StandardErrorListener() {
033: }
034:
035: /**
036: * Make a clean copy of this ErrorListener. This is necessary because the
037: * standard error listener is stateful (it remembers how many errors there have been)
038: */
039:
040: public StandardErrorListener makeAnother(int hostLanguage) {
041: return new StandardErrorListener();
042: }
043:
044: // Note, when the standard error listener is used, a new
045: // one is created for each transformation, because it holds
046: // the recovery policy and the warning count.
047:
048: /**
049: * Set output destination for error messages (default is System.err)
050: * @param writer The PrintStream to use for error messages
051: */
052:
053: public void setErrorOutput(PrintStream writer) {
054: errorOutput = writer;
055: }
056:
057: /**
058: * Get the error output stream
059: */
060:
061: public PrintStream getErrorOutput() {
062: return errorOutput;
063: }
064:
065: /**
066: * Set the recovery policy
067: */
068:
069: public void setRecoveryPolicy(int policy) {
070: recoveryPolicy = policy;
071: }
072:
073: /**
074: * Get the recovery policy
075: */
076:
077: public int getRecoveryPolicy() {
078: return recoveryPolicy;
079: }
080:
081: /**
082: * Receive notification of a warning.
083: *
084: * <p>Transformers can use this method to report conditions that
085: * are not errors or fatal errors. The default behaviour is to
086: * take no action.</p>
087: *
088: * <p>After invoking this method, the Transformer must continue with
089: * the transformation. It should still be possible for the
090: * application to process the document through to the end.</p>
091: *
092: * @param exception The warning information encapsulated in a
093: * transformer exception.
094: *
095: * @throws javax.xml.transform.TransformerException if the application
096: * chooses to discontinue the transformation.
097: *
098: * @see javax.xml.transform.TransformerException
099: */
100:
101: public void warning(TransformerException exception)
102: throws TransformerException {
103:
104: if (recoveryPolicy == Configuration.RECOVER_SILENTLY) {
105: // do nothing
106: return;
107: }
108:
109: if (errorOutput == null) {
110: // can happen after deserialization
111: errorOutput = System.err;
112: }
113: String message = "";
114: if (exception.getLocator() != null) {
115: message = getLocationMessage(exception) + "\n ";
116: }
117: message += wordWrap(getExpandedMessage(exception));
118:
119: if (exception instanceof ValidationException) {
120: errorOutput.println("Validation error " + message);
121:
122: } else {
123: errorOutput.println("Warning: " + message);
124: warningCount++;
125: if (warningCount > 25) {
126: errorOutput
127: .println("No more warnings will be displayed");
128: recoveryPolicy = Configuration.RECOVER_SILENTLY;
129: warningCount = 0;
130: }
131: }
132: }
133:
134: /**
135: * Receive notification of a recoverable error.
136: *
137: * <p>The transformer must continue to provide normal parsing events
138: * after invoking this method. It should still be possible for the
139: * application to process the document through to the end.</p>
140: *
141: * <p>The action of the standard error listener depends on the
142: * recovery policy that has been set, which may be one of RECOVER_SILENTLY,
143: * RECOVER_WITH_WARNING, or DO_NOT_RECOVER
144: *
145: * @param exception The error information encapsulated in a
146: * transformer exception.
147: *
148: * @throws TransformerException if the application
149: * chooses to discontinue the transformation.
150: *
151: * @see TransformerException
152: */
153:
154: public void error(TransformerException exception)
155: throws TransformerException {
156: if (recoveryPolicy == Configuration.RECOVER_SILENTLY) {
157: // do nothing
158: return;
159: }
160: if (errorOutput == null) {
161: // can happen after deserialization
162: errorOutput = System.err;
163: }
164: String message = getLocationMessage(exception) + "\n "
165: + wordWrap(getExpandedMessage(exception));
166:
167: if (exception instanceof ValidationException) {
168: errorOutput.println("Validation error " + message);
169:
170: } else if (recoveryPolicy == Configuration.RECOVER_WITH_WARNINGS) {
171: errorOutput.println("Recoverable error " + message);
172: warningCount++;
173: if (warningCount > 25) {
174: errorOutput
175: .println("No more warnings will be displayed");
176: recoveryPolicy = Configuration.RECOVER_SILENTLY;
177: warningCount = 0;
178: }
179: } else {
180: errorOutput.println("Recoverable error " + message);
181: errorOutput
182: .println("Processing terminated because error recovery is disabled");
183: throw new DynamicError(exception);
184: }
185: }
186:
187: /**
188: * Receive notification of a non-recoverable error.
189: *
190: * <p>The application must assume that the transformation cannot
191: * continue after the Transformer has invoked this method,
192: * and should continue (if at all) only to collect
193: * addition error messages. In fact, Transformers are free
194: * to stop reporting events once this method has been invoked.</p>
195: *
196: * @param exception The error information encapsulated in a
197: * transformer exception.
198: *
199: * @throws TransformerException if the application
200: * chooses to discontinue the transformation.
201: *
202: * @see TransformerException
203: */
204:
205: public void fatalError(TransformerException exception)
206: throws TransformerException {
207: if (exception instanceof XPathException
208: && ((XPathException) exception).hasBeenReported()) {
209: // don't report the same error twice
210: return;
211: }
212: if (errorOutput == null) {
213: // can happen after deserialization
214: errorOutput = System.err;
215: }
216: String message = (exception instanceof ValidationException ? "Validation error "
217: : "Error ")
218: + getLocationMessage(exception)
219: + "\n "
220: + wordWrap(getExpandedMessage(exception));
221: errorOutput.println(message);
222: if (exception instanceof XPathException) {
223: ((XPathException) exception).setHasBeenReported();
224: // probably redundant. It's the caller's job to set this flag, because there might be
225: // a non-standard error listener in use.
226: }
227: }
228:
229: /**
230: * Get a string identifying the location of an error.
231: */
232:
233: public static String getLocationMessage(TransformerException err) {
234: SourceLocator loc = err.getLocator();
235: while (loc == null) {
236: if (err.getException() instanceof TransformerException) {
237: err = (TransformerException) err.getException();
238: loc = err.getLocator();
239: } else if (err.getCause() instanceof TransformerException) {
240: err = (TransformerException) err.getCause();
241: loc = err.getLocator();
242: } else {
243: return "";
244: }
245: }
246: XPathContext context = null;
247: if (err instanceof DynamicError) {
248: context = ((DynamicError) err).getXPathContext();
249: }
250: return getLocationMessage(loc, context);
251: }
252:
253: private static String getLocationMessage(SourceLocator loc,
254: XPathContext context) {
255: String locmessage = "";
256: String systemId = null;
257: int lineNumber = -1;
258: if (loc instanceof DOMLocator) {
259: locmessage += "at "
260: + ((DOMLocator) loc).getOriginatingNode()
261: .getNodeName() + ' ';
262: } else if (loc instanceof NodeInfo) {
263: locmessage += "at " + ((NodeInfo) loc).getDisplayName()
264: + ' ';
265: } else if (loc instanceof InstructionInfoProvider) {
266: String instructionName = getInstructionName(
267: ((InstructionInfoProvider) loc), context);
268: if (!"".equals(instructionName)) {
269: locmessage += "at " + instructionName + ' ';
270: }
271: systemId = ((InstructionInfoProvider) loc)
272: .getInstructionInfo().getSystemId();
273: lineNumber = ((InstructionInfoProvider) loc)
274: .getInstructionInfo().getLineNumber();
275: }
276: if (lineNumber == -1) {
277: lineNumber = loc.getLineNumber();
278: }
279: if (lineNumber != -1) {
280: locmessage += "on line " + lineNumber + ' ';
281: }
282: if (loc.getColumnNumber() != -1) {
283: locmessage += "column " + loc.getColumnNumber() + ' ';
284: }
285: if (systemId == null) {
286: systemId = loc.getSystemId();
287: }
288: if (systemId != null) {
289: locmessage += "of " + systemId + ':';
290: }
291: return locmessage;
292: }
293:
294: /**
295: * Get a string containing the message for this exception and all contained exceptions
296: */
297:
298: public static String getExpandedMessage(TransformerException err) {
299:
300: String code = null;
301: if (err instanceof XPathException) {
302: code = ((XPathException) err).getErrorCodeLocalPart();
303: } else if (err.getException() instanceof XPathException) {
304: code = ((XPathException) err.getException())
305: .getErrorCodeLocalPart();
306: }
307: String message = "";
308: if (code != null) {
309: message = code;
310: }
311:
312: Throwable e = err;
313: while (true) {
314: if (e == null) {
315: break;
316: }
317: String next = e.getMessage();
318: if (next == null)
319: next = "";
320: if (next.startsWith("net.sf.saxon.trans.StaticError: ")) {
321: next = next.substring(next.indexOf(": ") + 2);
322: }
323: if (!("TRaX Transform Exception".equals(next) || message
324: .endsWith(next))) {
325: if (!"".equals(message)
326: && !message.trim().endsWith(":")) {
327: message += ": ";
328: }
329: message += next;
330: }
331: if (e instanceof TransformerException) {
332: e = ((TransformerException) e).getException();
333: } else if (e instanceof SAXException) {
334: e = ((SAXException) e).getException();
335: } else {
336: // e.printStackTrace();
337: break;
338: }
339: }
340:
341: return message;
342: }
343:
344: /**
345: * Extract a name identifying the instruction at which an error occurred
346: */
347:
348: private static String getInstructionName(
349: InstructionInfoProvider inst, XPathContext context) {
350: // TODO: subclass this for XSLT and XQuery
351: if (context == null) {
352: return "";
353: }
354: try {
355: InstructionInfo info = inst.getInstructionInfo();
356: int construct = info.getConstructType();
357: if (construct < 1024
358: && construct != StandardNames.XSL_FUNCTION
359: && construct != StandardNames.XSL_TEMPLATE) {
360: // it's a standard name
361: if (context.getController().getExecutable()
362: .getHostLanguage() == Configuration.XSLT) {
363: return StandardNames.getDisplayName(construct);
364: } else {
365: String s = StandardNames.getDisplayName(construct);
366: int colon = s.indexOf(':');
367: if (colon > 0) {
368: String local = s.substring(colon + 1);
369: if (local.equals("document")) {
370: return "document node constructor";
371: } else if (local.equals("text")
372: || s.equals("value-of")) {
373: return "text node constructor";
374: } else if (local.equals("element")) {
375: return "computed element constructor";
376: } else if (local.equals("attribute")) {
377: return "computed attribute constructor";
378: }
379: }
380: return s;
381: }
382: }
383: switch (construct) {
384: case Location.LITERAL_RESULT_ELEMENT: {
385: int fp = info.getObjectNameCode();
386: String name = "element constructor";
387: if (context != null) {
388: name += " <"
389: + context.getNamePool().getDisplayName(fp)
390: + '>';
391: }
392: return name;
393: }
394: case Location.LITERAL_RESULT_ATTRIBUTE: {
395: int fp = info.getObjectNameCode();
396: String name = "attribute constructor";
397: if (context != null) {
398: name += ' '
399: + context.getNamePool().getDisplayName(fp)
400: + "=\"{...}\"";
401: }
402: return name;
403: }
404: case StandardNames.XSL_FUNCTION: {
405: int fp = info.getObjectNameCode();
406: String name = "function";
407: if (context != null) {
408: name += ' '
409: + context.getNamePool().getDisplayName(fp)
410: + "()";
411: }
412: return name;
413: }
414: case StandardNames.XSL_TEMPLATE: {
415: int fp = info.getObjectNameCode();
416: String name = "template";
417: if (context != null && fp != -1) {
418: name += " name=\""
419: + context.getNamePool().getDisplayName(fp)
420: + '\"';
421: }
422: return name;
423: }
424: default:
425: return "";
426: }
427:
428: } catch (Exception err) {
429: return "";
430: }
431: }
432:
433: /**
434: * Wordwrap an error message into lines of 72 characters or less (if possible)
435: */
436:
437: private static String wordWrap(String message) {
438: int nl = message.indexOf('\n');
439: if (nl < 0) {
440: nl = message.length();
441: }
442: if (nl > 100) {
443: int i = 90;
444: while (message.charAt(i) != ' ' && i > 0) {
445: i--;
446: }
447: if (i > 10) {
448: return message.substring(0, i) + "\n "
449: + wordWrap(message.substring(i + 1));
450: } else {
451: return message;
452: }
453: } else if (nl < message.length()) {
454: return message.substring(0, nl) + '\n'
455: + wordWrap(message.substring(nl + 1));
456: } else {
457: return message;
458: }
459: }
460:
461: }
462:
463: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
464: // you may not use this file except in compliance with the License. You may obtain a copy of the
465: // License at http://www.mozilla.org/MPL/
466: //
467: // Software distributed under the License is distributed on an "AS IS" basis,
468: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
469: // See the License for the specific language governing rights and limitations under the License.
470: //
471: // The Original Code is: all this file.
472: //
473: // The Initial Developer of the Original Code is Michael H. Kay.
474: //
475: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
476: //
477: // Contributor(s):
478: // Portions marked "e.g." are from Edwin Glaser (edwin@pannenleiter.de)
479: //
|