All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.lib.StandardDiagnostics Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.lib;

import net.sf.saxon.Controller;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.*;
import net.sf.saxon.expr.parser.Loc;
import net.sf.saxon.expr.parser.XPathParser;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.serialize.charcode.UTF16CharacterSet;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.KeyDefinition;
import net.sf.saxon.trans.Mode;
import net.sf.saxon.trans.rules.BuiltInRuleSet;
import net.sf.saxon.trans.rules.Rule;
import net.sf.saxon.tree.AttributeLocation;

import net.sf.saxon.tree.util.Navigator;
import net.sf.saxon.type.Type;
import net.sf.saxon.type.ValidationException;
import net.sf.saxon.type.ValidationFailure;
import net.sf.saxon.value.StringValue;
import net.sf.saxon.z.IntIterator;

import javax.xml.transform.SourceLocator;
import javax.xml.transform.dom.DOMLocator;
import java.util.List;

/**
 * This class is an abstract superclass of classes such as the {@link StandardErrorListener}
 * and {@link StandardInvalidityHandler}, and exists to provide common utility methods for these
 * classes, and for similar user-written classes if required
 */

public class StandardDiagnostics {

    public StandardDiagnostics() {}

    /**
     * Construct a message identifying the location of an error
     * @param loc the location of the error
     * @return a message describing the location
     */

    public String getLocationMessageText(SourceLocator loc) {
        String locMessage = "";
        String systemId = null;
        NodeInfo node = null;
        String path;
        String nodeMessage = null;
        int lineNumber = -1;
        if (loc == null) {
            loc = Loc.NONE;
        }
        if (loc instanceof XPathParser.NestedLocation) {
            loc = ((XPathParser.NestedLocation) loc).getContainingLocation();
        }
        if (loc instanceof AttributeLocation) {
            AttributeLocation saLoc = (AttributeLocation) loc;
            nodeMessage = "in " + saLoc.getElementName().getDisplayName();
            if (saLoc.getAttributeName() != null) {
                nodeMessage += "/@" + saLoc.getAttributeName();
            }
            nodeMessage += ' ';
        } else if (loc instanceof DOMLocator) {
            nodeMessage = "at " + ((DOMLocator) loc).getOriginatingNode().getNodeName() + ' ';
        } else if (loc instanceof NodeInfo) {
            node = (NodeInfo) loc;
            nodeMessage = "at " + node.getDisplayName() + ' ';
        } else if (loc instanceof ValidationException && (node = ((ValidationException) loc).getNode()) != null) {
            nodeMessage = "at " + node.getDisplayName() + ' ';
        } else if (loc instanceof ValidationException && loc.getLineNumber() == -1 && (path = ((ValidationException) loc).getPath()) != null) {
            nodeMessage = "at " + path + ' ';
        } else if (loc instanceof Instruction) {
            String instructionName = getInstructionName((Instruction) loc);
            if (!"".equals(instructionName)) {
                nodeMessage = "at " + instructionName + ' ';
            }
            systemId = loc.getSystemId();
            lineNumber = loc.getLineNumber();
        } else if (loc instanceof Actor) {
            String kind = "procedure";
            if (loc instanceof UserFunction) {
                kind = "function";
            } else if (loc instanceof NamedTemplate) {
                kind = "template";
            } else if (loc instanceof AttributeSet) {
                kind = "attribute-set";
            } else if (loc instanceof KeyDefinition) {
                kind = "key";
            } else if (loc instanceof GlobalParam) {
                kind = "parameter";
            } else if (loc instanceof GlobalVariable) {
                kind = "variable";
            } else if (loc instanceof Mode) {
                kind = "mode";
            }
            systemId = loc.getSystemId();
            lineNumber = loc.getLineNumber();
            nodeMessage = "at " + kind + " ";
            StructuredQName name = ((Actor) loc).getComponentName();
            if (name != null) {
                String n = name.toString();
                if (n.equals("xsl:unnamed")) {
                    n = "(unnamed)";
                }
                nodeMessage += n;
                nodeMessage += " ";
            }
        }
        if (lineNumber == -1) {
            lineNumber = loc.getLineNumber();
        }
        boolean containsLineNumber = lineNumber > 0;
        if (node != null && !containsLineNumber) {
            nodeMessage = "at " + Navigator.getPath(node) + ' ';
        }
        if (nodeMessage != null) {
            locMessage += nodeMessage;
        }
        if (containsLineNumber) {
            locMessage += "on line " + lineNumber + ' ';
            if (loc.getColumnNumber() != -1) {
                locMessage += "column " + loc.getColumnNumber() + ' ';
            }
        }

        if (systemId != null && systemId.isEmpty()) {
            systemId = null;
        }
        if (systemId == null) {
            try {
                systemId = loc.getSystemId();
            } catch (Exception err) {
                err.printStackTrace();
                // no action (can fail with NPE if the expression tree is corrupt)
            }
        }
        if (systemId != null && !systemId.isEmpty()) {
            locMessage += (containsLineNumber ? "of " : "in ") + abbreviateLocationURI(systemId) + ':';
        }
        return locMessage;
    }

    /**
     * Extract a name or phrase identifying the instruction at which an error occurred.
     * This default implementation invokes the static method
     * {@link #getInstructionNameDefault(Instruction)}.
     *
     * @param inst the instruction in question
     * @return the name or description of the instruction,
     * in user-meaningful terms
     */

    public String getInstructionName(Instruction inst) {
        return getInstructionNameDefault(inst);
    }

    /**
     * Extract a name or phrase identifying the instruction at which an error occurred.
     * This default implementation uses terminology that is neutral between XQuery and XSLT,
     * for example "text node constructor" in preference to "xsl:text".
     *
     * @param inst the instruction in question
     * @return the name or description of the instruction,
     * in user-meaningful terms
     */

    public static String getInstructionNameDefault(Instruction inst) {
        try {
            if (inst instanceof FixedElement) {
                StructuredQName qName = inst.getObjectName();
                return "element constructor <" + qName.getDisplayName() + '>';
            } else if (inst instanceof FixedAttribute) {
                StructuredQName qName = inst.getObjectName();
                return "attribute constructor " + qName.getDisplayName() + "=\"{...}\"";
            }
            int construct = inst.getInstructionNameCode();
            if (construct < 0) {
                return "";
            }
            if (construct < 1024 &&
                    construct != StandardNames.XSL_FUNCTION &&
                    construct != StandardNames.XSL_TEMPLATE) {
                // it's a standard name
                if (inst.getPackageData().isXSLT()) {
                    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";
                        } else if (local.equals("comment")) {
                            return "comment constructor";
                        } else if (local.equals("processing-instruction")) {
                            return "processing-instruction constructor";
                        } else if (local.equals("namespace")) {
                            return "namespace node constructor";
                        }
                    }
                    return s;
                }
            } else {
                return "";
            }

        } catch (Exception err) {
            return "";
        }
    }


    /**
     * Print a stack trace to a specified output destination
     *
     * @param context the XPath dynamic execution context (which holds the head of a linked
     *                list of context objects, representing the execution stack)
     * @param out     the print stream to which the stack trace will be output
     * @param level   the level of detail: 0=none, 1=name and location of function/template,
     *                2=values of variables
     */

    public void logStackTrace(XPathContext context, Logger out, int level) {
        if (level > 0) {
            int depth = 20;
            while (depth-- > 0 && context != null) {
                Component component = context.getCurrentComponent();
                if (component != null) {
                    if (component.getActor() instanceof Mode) {
                        Rule rule = context.getCurrentTemplateRule();
                        if (rule != null) {
                            StringBuilder sb = new StringBuilder();
                            Location loc = rule.getPattern().getLocation();
                            sb.append("  In template rule with match=\"")
                                    .append(rule.getPattern().toShortString())
                                    .append("\" ");
                            if (loc != null && loc.getLineNumber() != -1) {
                                sb.append("on line ").append(loc.getLineNumber()).append(" ");
                            }
                            if (loc != null && loc.getSystemId() != null) {
                                sb.append("of ").append(abbreviateLocationURI(loc.getSystemId()));
                            }
                            out.error(sb.toString());
                        }
                    } else {
                        out.error(getLocationMessageText(component.getActor()).replace("$at ", "In "));
                    }
                }
                try {
                    context.getStackFrame().getStackFrameMap().showStackFrame(context, out);
                } catch (Exception e) {
                    // no action
                }
                context = context.getMajorContext();

                ContextOriginator originator = ((XPathContextMajor) context).getOrigin();
                if (originator == null || originator instanceof Controller) {
                    return;
                } else {
                    out.error("     invoked by " + showOriginator(originator));
                }
                context = context.getCaller();
            }
        }
    }

    /**
     * Produce text identifying a construct that originates the context for an error
     * @param originator the {@code ContextOriginator} responsible for creating a new
     *                   context
     * @return message text (suitable for use in a phrase such as "called by XXX") for
     * inclusion in an error message.
     */

    protected String showOriginator(ContextOriginator originator) {
        StringBuilder sb = new StringBuilder();
        if (originator == null) {
            sb.append("unknown caller (null)");
        } else if (originator instanceof Instruction) {
            sb.append(getInstructionName((Instruction) originator));
            if (originator instanceof CallTemplate && ((CallTemplate)originator).usesTailRecursion()) {
                sb.append(" (tail calls omitted)");
            }
            if (originator instanceof ApplyTemplates && ((ApplyTemplates) originator).useTailRecursion()) {
                sb.append(" (tail calls omitted)");
            }
        } else if (originator instanceof UserFunctionCall) {
            sb.append("function call");
        } else if (originator instanceof Controller) {
            sb.append("external application");
        } else if (originator instanceof BuiltInRuleSet) {
            sb.append("built-in template rule (").append(((BuiltInRuleSet) originator).getName()).append(")");
        } else if (originator instanceof KeyDefinition) {
            sb.append("xsl:key definition");
        } else if (originator instanceof GlobalParam) {
            sb.append("global parameter ").append(((GlobalParam) originator).getVariableQName().getDisplayName());
        } else if (originator instanceof GlobalVariable) {
            sb.append(((GlobalVariable) originator).getDescription());
        } else {
            sb.append("unknown caller (").append(originator.getClass()).append(")");
        }
        if (originator instanceof Locatable) {
            Location loc = ((Locatable) originator).getLocation();
            if (loc.getLineNumber() != -1) {
                sb.append(" at ").append(loc.getSystemId() == null ? "line " : (loc.getSystemId() + "#"));
                sb.append(loc.getLineNumber());
            }
        }
        return sb.toString();
    }


    /**
     * A {@link ValidationFailure} relating to a failing assertion contains a (possibly empty)
     * list of nodes responsible for the failure. This may be the node to which the assertion
     * applies, or in the case of an assertion in the form {@code every $x in XX satisfies P}
     * it may be the node in XX for which P was not satisfied. This method converts this list
     * of nodes into a string suitable for inclusion in error messages, typically as a complete
     * sentence. If the list of nodes is empty, the returned string should normally be empty.
     *
     * 

The default implementation displays a message over several lines, starting with * "Nodes for which the assertion fails:", and continuing with one line per node. In the * typical case where the nodes are elements, subsequent lines will take the form * "element(N) on line L column C of file.xml" if the location is not, or "at x/y/z" * (where x/y/z is a path to the node) otherwise.

* *

The method is provided so that it can be overridden in a subclass.

* * @param failure the validation failure being reported. * @return a string describing the list of nodes contributing to the failure of an * assertion. */ protected String formatListOfOffendingNodes(ValidationFailure failure) { StringBuilder message = new StringBuilder(); List offendingNodes = failure.getOffendingNodes(); if (!offendingNodes.isEmpty()) { message.append("\n Nodes for which the assertion fails:"); for (NodeInfo offender : offendingNodes) { String nodeDesc = Type.displayTypeName(offender); if (offender.getNodeKind() == Type.TEXT) { nodeDesc += " " + Err.wrap(offender.getUnicodeStringValue(), Err.VALUE); } if (offender.getLineNumber() != -1) { nodeDesc += " on line " + offender.getLineNumber(); if (offender.getColumnNumber() != -1) { nodeDesc += " column " + offender.getColumnNumber(); } if (offender.getSystemId() != null) { nodeDesc += " of " + offender.getSystemId(); } } else { nodeDesc += " at " + Navigator.getPath(offender); } message.append("\n * ").append(nodeDesc); } } return message.toString(); } /** * Abbreviate a URI for use in diagnostics. *

* This default implementation invokes the static method {@link #abbreviateLocationURIDefault(String)}. *

* This method is intended for use only for location URIs, not for namespace URIs. * * @param uri the URI to be abbreviated * @return the abbreviated URI */ /*@Nullable*/ public String abbreviateLocationURI(String uri) { return abbreviateLocationURIDefault(uri); } /** * Abbreviate a URI for use in diagnostics. *

* This default implementation displays the part of the URI after the last slash. *

* This method is intended for use only for location URIs, not for namespace URIs. * * @param uri the URI to be abbreviated * @return the abbreviated URI */ /*@Nullable*/ public static String abbreviateLocationURIDefault(String uri) { if (uri == null) { return "*unknown*"; } int slash = uri.lastIndexOf('/'); if (slash >= 0 && slash < uri.length() - 1) { return uri.substring(slash + 1); } else { return uri; } } /** * Variable defining an absolute limit on the length of an error message; * any message longer than this will be truncated by the {@link #wordWrap(String)} * method. The value can be assigned. Default value is 2000. */ public int MAX_MESSAGE_LENGTH = 2000; /** * Variable defining a threshold for word-wrapping a message. If a message * is longer than this, then the {@link #wordWrap(String)} method will attempt * to break it into shorter lines. The value can be assigned. Default value is 100. */ public int MAX_MESSAGE_LINE_LENGTH = 100; /** * Variable defining a minimum length for parts of a word-wrapped a message. The * {@link #wordWrap(String)} method will not split a message line if it would result * in a line shorter than this. The value can be assigned. Default value is 10. */ public int MIN_MESSAGE_LINE_LENGTH = 10; /** * Variable defining a target line length for messages. If word-wrapping takes place, * then it will take place at the last space character found before this column * position. The value can be assigned. Default value is 90. */ public int TARGET_MESSAGE_LINE_LENGTH = 90; /** * Wordwrap an error message into lines of {@link #TARGET_MESSAGE_LINE_LENGTH} * characters or less (if possible). Note that existing newlines within the message * will be retained. * * @param message the message to be word-wrapped * @return the message after applying word-wrapping */ public String wordWrap(String message) { if (message.length() > MAX_MESSAGE_LENGTH) { message = message.substring(0, MAX_MESSAGE_LENGTH); } int nl = message.indexOf('\n'); if (nl < 0) { nl = message.length(); } if (nl > MAX_MESSAGE_LINE_LENGTH) { int i = TARGET_MESSAGE_LINE_LENGTH; while (message.charAt(i) != ' ' && i > 0) { i--; } if (i > MIN_MESSAGE_LINE_LENGTH) { 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; } } /** * Expand any special characters appearing in a message. In the default implementation, * special characters will be output as themselves, followed by a hex codepoint in the * form [xHHHHH]: for example {@code §[xA7]} * * @param in the message to be expanded * @param threshold the codepoint above which characters are considered special. * @return the expanded message */ public String expandSpecialCharacters(String in, int threshold) { if (threshold >= UTF16CharacterSet.NONBMP_MAX) { return in; } StringValue str = new StringValue(in); StringBuilder fsb = new StringBuilder(str.length32() * 2); IntIterator iter = str.codePoints(); while (iter.hasNext()) { int ch = iter.next(); fsb.appendCodePoint(ch); if (ch > threshold) { fsb.append("[x"); fsb.append(Integer.toHexString(ch)); fsb.append("]"); } } return fsb.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy