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

net.sf.saxon.style.XSLTemplate 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.style;

import net.sf.saxon.Configuration;
import net.sf.saxon.expr.Component;
import net.sf.saxon.expr.ErrorExpression;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.StaticProperty;
import net.sf.saxon.expr.instruct.NamedTemplate;
import net.sf.saxon.expr.instruct.SlotManager;
import net.sf.saxon.expr.instruct.TemplateRule;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.lib.Feature;
import net.sf.saxon.lib.Logger;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.Pattern;
import net.sf.saxon.trans.*;
import net.sf.saxon.trans.rules.Rule;
import net.sf.saxon.trans.rules.RuleManager;
import net.sf.saxon.type.Affinity;
import net.sf.saxon.type.AnyItemType;
import net.sf.saxon.type.ErrorType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.value.BigDecimalValue;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.Whitespace;

import java.util.*;
import java.util.function.Supplier;

/**
 * An xsl:template element in the style sheet.
 */

public final class XSLTemplate extends StyleElement implements StylesheetComponent {

    private String matchAtt = null;
    private String modeAtt = null;
    private String nameAtt = null;
    private String priorityAtt = null;
    private String asAtt = null;
    private String visibilityAtt = null;

    private StructuredQName[] modeNames;
    private String diagnosticId;
    private Pattern match;
    private boolean prioritySpecified;
    private double priority;
    private SlotManager stackFrameMap;
    // A compiled named template exists if the template has a name
    private NamedTemplate compiledNamedTemplate;
    // A set of compiled template rules exists if the template has a match pattern: one TemplateRule for each mode
    private final Map compiledTemplateRules = new HashMap<>();
    private SequenceType requiredType = SequenceType.ANY_SEQUENCE;
    private boolean declaresRequiredType = false;
    private boolean hasRequiredParams = false;
    private boolean isTailRecursive = false;
    private Visibility visibility = Visibility.PRIVATE;
    private ItemType requiredContextItemType = AnyItemType.getInstance();
    private boolean mayOmitContextItem = true;
    //private boolean maySupplyContextItem = true;
    private boolean absentFocus = false;
    private boolean jitCompilationDone = false;
    private boolean explaining;
    //private Expression body;

    /**
     * Get the corresponding NamedTemplate object that results from the compilation of this
     * StylesheetComponent
     */
    @Override
    public NamedTemplate getActor() {
        return compiledNamedTemplate;
    }

//    public Expression getBody() {
//        return body;
//    }

    @Override
    public void setCompilation(Compilation compilation) {
        super.setCompilation(compilation);
        //compiledNamedTemplate.setPackageData(compilation.getPackageData());
    }

    /**
     * Ask whether this node is a declaration, that is, a permitted child of xsl:stylesheet
     * (including xsl:include and xsl:import).
     *
     * @return true for this element
     */

    @Override
    public boolean isDeclaration() {
        return true;
    }

    /**
     * Ask whether the compilation of the template should be deferred
     *
     * @param compilation the compilation
     * @return true if compilation should be deferred
     */

    public boolean isDeferredCompilation(Compilation compilation) {
        return compilation.isPreScan() && getTemplateName() == null && !compilation.isLibraryPackage();
    }

    /**
     * Determine whether this type of element is allowed to contain a template-body
     *
     * @return true: yes, it may contain a template-body
     */

    @Override
    protected boolean mayContainSequenceConstructor() {
        return true;
    }

    @Override
    protected boolean mayContainParam() {
        return true;
    }

    @Override
    protected boolean isWithinDeclaredStreamableConstruct() {
        try {
            for (Mode m : getApplicableModes()) {
                if (m.isDeclaredStreamable()) {
                    return true;
                }
            }
        } catch (XPathException e) {
            return false;
        }
        return false;
    }

    /**
     * Set the required context item type. Used when there is an xsl:context-item child element
     *
     * @param type         the required context item type
     * @param mayBeOmitted true if the context item may be absent
     * @param absentFocus  true if use=absent is specified
     */

    public void setContextItemRequirements(ItemType type, boolean mayBeOmitted, boolean absentFocus) {
        requiredContextItemType = type;
        mayOmitContextItem = mayBeOmitted;
        this.absentFocus = absentFocus;
    }

    /**
     * Specify that xsl:param and xsl:context-item are permitted children
     */

    @Override
    protected boolean isPermittedChild(StyleElement child) {
        return child instanceof XSLLocalParam || child.getFingerprint() == StandardNames.XSL_CONTEXT_ITEM;
    }

    /**
     * Return the name of this template. Note that this may
     * be called before prepareAttributes has been called.
     *
     * @return the name of the template as a Structured QName.
     */

    /*@Nullable*/
    public StructuredQName getTemplateName() {

        if (getObjectName() == null) {
            // allow for forwards references
            String nameAtt = getAttributeValue(NamespaceUri.NULL, "name");
            if (nameAtt != null) {
                setObjectName(makeQName(nameAtt, null, "name"));
            }
        }
        return getObjectName();
    }

    @Override
    public SymbolicName getSymbolicName() {
        if (getTemplateName() == null) {
            return null;
        } else {
            return new SymbolicName(StandardNames.XSL_TEMPLATE, getTemplateName());
        }
    }

    public ItemType getRequiredContextItemType() {
        return requiredContextItemType;
    }

    public boolean isMayOmitContextItem() {
        return mayOmitContextItem;
    }


    @Override
    public void checkCompatibility(Component component) {
        NamedTemplate other = (NamedTemplate) component.getActor();
        if (!getSymbolicName().equals(other.getSymbolicName())) {
            throw new IllegalArgumentException();
        }

        SequenceType req = requiredType == null ? SequenceType.ANY_SEQUENCE : requiredType;
        if (!req.equals(other.getRequiredType())) {
            compileError("The overriding template has a different required type from the overridden template", "XTSE3070");
            return;
        }

        if (!requiredContextItemType.equals(other.getRequiredContextItemType()) ||
                mayOmitContextItem != other.isMayOmitContextItem() ||
                absentFocus != other.isAbsentFocus()) {
            compileError("The required context item for the overriding template differs from that of the overridden template", "XTSE3070");
            return;
        }

        List otherParams = other.getLocalParamDetails();
        Set overriddenParams = new HashSet<>();
        for (NamedTemplate.LocalParamInfo lp0 : otherParams) {
            XSLLocalParam lp1 = getParam(lp0.name);
            if (lp1 == null) {
                if (!lp0.isTunnel) {
                    compileError("The overridden template declares a parameter " +
                                         lp0.name.getDisplayName() + " which is not declared in the overriding template", "XTSE3070");
                }
                return;
            }
            if (!lp1.getRequiredType().equals(lp0.requiredType)) {
                lp1.compileError("The parameter " +
                                         lp0.name.getDisplayName() + " has a different required type in the overridden template", "XTSE3070");
                return;
            }
            if (lp1.isRequiredParam() != lp0.isRequired && !lp0.isTunnel) {
                lp1.compileError("The parameter " +
                                         lp0.name.getDisplayName() + " is " +
                                         (lp1.isRequiredParam() ? "required" : "optional") +
                                         " in the overriding template, but " +
                                         (lp0.isRequired ? "required" : "optional") +
                                         " in the overridden template", "XTSE3070");
                return;
            }
            if (lp1.isTunnelParam() != lp0.isTunnel) {
                lp1.compileError("The parameter " +
                                         lp0.name.getDisplayName() + " is a " +
                                         (lp1.isTunnelParam() ? "tunnel" : "non-tunnel") +
                                         " parameter in the overriding template, but " +
                                         (lp0.isTunnel ? "tunnel" : "non-tunnel") +
                                         " parameter in the overridden template", "XTSE3070");
                return;
            }
            overriddenParams.add(lp0.name);
        }

        for (NodeInfo param : children(XSLLocalParam.class::isInstance)) {
            if (!overriddenParams.contains(((XSLLocalParam) param).getObjectName()) &&
                    ((XSLLocalParam) param).isRequiredParam()) {
                ((XSLLocalParam) param).compileError(
                        "An overriding template cannot introduce a required parameter that is not declared in the overridden template", "XTSE3070");
            }
        }

    }

    public XSLLocalParam getParam(StructuredQName name) {
        for (NodeInfo param : children(XSLLocalParam.class::isInstance)) {
            if (name.equals(((XSLLocalParam) param).getObjectName())) {
                return (XSLLocalParam) param;
            }
        }
        return null;
    }


    @Override
    protected void prepareAttributes() {

        AttributeMap atts = attributes();
        String extraAsAtt = null;

        for (AttributeInfo att : atts) {
            NodeName name = att.getNodeName();
            String f = name.getDisplayName();
            if (f.equals("mode")) {
                modeAtt = Whitespace.trim(att.getValue());
            } else if (f.equals("name")) {
                nameAtt = Whitespace.trim(att.getValue());
            } else if (f.equals("match")) {
                matchAtt = att.getValue();
            } else if (f.equals("priority")) {
                priorityAtt = Whitespace.trim(att.getValue());
            } else if (f.equals("as")) {
                asAtt = att.getValue();
            } else if (f.equals("visibility")) {
                visibilityAtt = Whitespace.trim(att.getValue());
            } else if (name.hasURI(NamespaceUri.SAXON)) {
                isExtensionAttributeAllowed(name.getDisplayName());
                if (name.getLocalPart().equals("as")) {
                    extraAsAtt = att.getValue();
                } else if (name.getLocalPart().equals("explain")) {
                    explaining = isYes(Whitespace.trim(att.getValue()));
                }
            } else {
                checkUnknownAttribute(name);
            }
        }
        try {
            if (modeAtt == null) {
                if (matchAtt != null) {
                    // XSLT 3.0 allows the default mode to be specified on any element
                    StructuredQName defaultMode = getDefaultMode();
                    if (defaultMode == null) {
                        defaultMode = Mode.UNNAMED_MODE_NAME;
                    }
                    modeNames = new StructuredQName[1];
                    modeNames[0] = defaultMode;
                }
            } else {
                if (matchAtt == null) {
                    compileError("The mode attribute must be absent if the match attribute is absent", "XTSE0500");
                }
                getModeNames();
            }
        } catch (XPathException err) {
            err.maybeSetErrorCode("XTSE0280");
            if (err.getErrorCodeLocalPart().equals("XTSE0020")) {
                err.setErrorCode("XTSE0550");
            }
            err.setIsStaticError(true);
            compileError(err);
        }

        if (nameAtt != null) {
            if (getObjectName() == null) {
                StructuredQName qName = makeQName(nameAtt, "XTSE0280", "name");
                setObjectName(qName);
            }
            if (compiledNamedTemplate != null) {
                compiledNamedTemplate.setTemplateName(getObjectName());
            }
            diagnosticId = nameAtt;
        }

        prioritySpecified = priorityAtt != null;
        if (prioritySpecified) {
            if (matchAtt == null) {
                compileError("The priority attribute must be absent if the match attribute is absent", "XTSE0500");
            }
            try {
                // it's got to be a valid decimal, but we want it as a double, so parse it twice
                if (!BigDecimalValue.castableAsDecimal(priorityAtt)) {
                    compileError("Invalid numeric value for priority (" + priority + ')', "XTSE0530");
                }
                priority = Double.parseDouble(priorityAtt);
            } catch (NumberFormatException err) {
                // shouldn't happen
                compileError("Invalid numeric value for priority (" + priority + ')', "XTSE0530");
            }
        }

        if (matchAtt != null) {
            match = makePattern(matchAtt, "match");
            if (diagnosticId == null) {
                diagnosticId = "match=\"" + matchAtt + '\"';
                if (modeAtt != null) {
                    diagnosticId += " mode=\"" + modeAtt + '\"';
                }
            }
        }

        if (match == null && nameAtt == null) {
            compileError("xsl:template must have a name or match attribute (or both)", "XTSE0500");
        }
        if (asAtt != null) {
            try {
                requiredType = makeSequenceType(asAtt);
                declaresRequiredType = true;
            } catch (XPathException e) {
                compileErrorInAttribute(e.getMessage(), e.getErrorCodeLocalPart(), "as");
            }
        }

        if (extraAsAtt != null) {
            SequenceType extraResultType;
            declaresRequiredType = true;
            try {
                extraResultType = makeExtendedSequenceType(extraAsAtt);
            } catch (XPathException e) {
                compileErrorInAttribute(e.getMessage(), e.getErrorCodeLocalPart(), "saxon:as");
                extraResultType = requiredType; // error recovery
            }
            if (asAtt != null) {
                Affinity rel = getConfiguration().getTypeHierarchy().sequenceTypeRelationship(extraResultType, requiredType);
                if (rel == Affinity.SAME_TYPE || rel == Affinity.SUBSUMED_BY) {
                    requiredType = extraResultType;
                } else {
                    compileErrorInAttribute("When both are present, @saxon:as must be a subtype of @as", "SXER7TBA", "saxon:as");
                }
            } else {
                requiredType = extraResultType;
            }
        }

        if (visibilityAtt != null) {
            visibility = interpretVisibilityValue(visibilityAtt, "");
            if (nameAtt == null) {
                compileError("xsl:template/@visibility can be specified only if the template has a @name attribute", "XTSE0020");
            } else {
                compiledNamedTemplate.setDeclaredVisibility(getVisibility());
            }
        }
    }

    @Override
    public void processAllAttributes() throws XPathException {
//        String mode = getAttributeValue("mode");
//        mode = mode == null ? "" : Whitespace.trim(mode);
        if (!isDeferredCompilation(getCompilation())) {
            super.processAllAttributes();      //TODO - sort out the duplicated code. This repeats the code below
        } else {
            processDefaultCollationAttribute();
            processDefaultMode();
            staticContext = new ExpressionContext(this, null);
            processAttributes();

        }
    }

    /**
     * Return the list of mode names to which this template rule is applicable.
     *
     * @return the list of mode names. If the mode attribute is absent, #default is assumed.
     * If #default is present explicitly or implicitly, it is replaced by the default mode, taken
     * from the in-scope default-modes attribute, which defaults to #unnamed. The unnamed mode
     * is represented by {@link Mode#UNNAMED_MODE_NAME}. The token #all translates to
     * {@link Mode#OMNI_MODE}.
     * @throws XPathException if the attribute is invalid.
     */

    public StructuredQName[] getModeNames() throws XPathException {
        if (modeNames == null) {
            // modeAtt is a space-separated list of mode names, or "#default", or "#all"
            if (modeAtt == null) {
                modeAtt = getAttributeValue("mode");
                if (modeAtt == null) {
                    modeAtt = "#default";
                }
            }

            boolean allModes = false;
            String[] tokens = Whitespace.trim(modeAtt).split("[ \t\n\r]+");
            int count = tokens.length;

            modeNames = new StructuredQName[count];
            count = 0;
            for (String s : tokens) {
                StructuredQName mname;
                if ("#default".equals(s)) {
                    mname = getDefaultMode();
                    if (mname == null) {
                        mname = Mode.UNNAMED_MODE_NAME;
                    }
                } else if ("#unnamed".equals(s)) {
                    mname = Mode.UNNAMED_MODE_NAME;
                } else if ("#all".equals(s)) {
                    allModes = true;
                    mname = Mode.OMNI_MODE;
                } else {
                    mname = makeQName(s, "XTSE0550", "mode");
                }
                for (int e = 0; e < count; e++) {
                    if (modeNames[e].equals(mname)) {
                        compileError("In the list of modes, the value " + s + " is duplicated", "XTSE0550");
                    }
                }
                modeNames[count++] = mname;
            }
            if (allModes && (count > 1)) {
                compileError("mode='#all' cannot be combined with other modes", "XTSE0550");
            }
        }
        return modeNames;
    }

    /**
     * Get the modes to which this template rule applies
     *
     * @return the set of modes to which it applies
     * @throws XPathException should not happen
     */

    public Set getApplicableModes() throws XPathException {
        StructuredQName[] names = getModeNames();
        Set modes = new HashSet<>(names.length);
        RuleManager mgr = getPrincipalStylesheetModule().getRuleManager();
        for (StructuredQName name : names) {
            if (name.equals(Mode.OMNI_MODE)) {
                modes.add(mgr.getUnnamedMode());
                modes.addAll(mgr.getAllNamedModes());
            } else {
                Mode mode = mgr.obtainMode(name, false);
                if (mode != null) {
                    modes.add(mode);
                }
            }
        }
        return modes;
    }

    /**
     * Ask whether this is a template rule with mode="#all"
     * @return true if this is the case
     * @throws XPathException if the mode attribute is found to be invalid
     */

    public boolean isOmniMode() throws XPathException {
        for (StructuredQName name : getModeNames()) {
            if (name.equals(Mode.OMNI_MODE)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void validate(ComponentDeclaration decl) throws XPathException {
        stackFrameMap = getConfiguration().makeSlotManager();
        checkTopLevel("XTSE0010", true);

        // the check for duplicates is now done in the buildIndexes() method of XSLStylesheet
        if (match != null) {
            match = typeCheck("match", match);
            if (match.getItemType() instanceof ErrorType) {
                issueWarning("Pattern will never match anything", SaxonErrorCode.SXWN9015);
            }
            if (getPrincipalStylesheetModule().isDeclaredModes()) {
                RuleManager manager = getPrincipalStylesheetModule().getRuleManager();
                if (modeNames != null) {
                    for (StructuredQName name : modeNames) {
                        if (name.equals(Mode.UNNAMED_MODE_NAME) && !manager.isUnnamedModeExplicit()) {
                            compileError("The unnamed mode has not been declared in an xsl:mode declaration", "XTSE3085");
                        }
                        if (manager.obtainMode(name, false) == null) {
                            compileError("Mode name " + name.getDisplayName() + " has not been declared in an xsl:mode declaration", "XTSE3085");
                        }
                    }
                } else {
                    if (!manager.isUnnamedModeExplicit()) {
                        compileError("The unnamed mode has not been declared in an xsl:mode declaration", "XTSE3085");
                    }
                }
            }
            if (visibility == Visibility.ABSTRACT) {
                compileError("An abstract template must have no match attribute");
            }
        }

        // See if there are any required parameters.
        boolean hasContent = false;
        for (NodeInfo param : children(StyleElement.class::isInstance)) {
            if (param.getFingerprint() == StandardNames.XSL_CONTEXT_ITEM) {
                // no action
            } else if (param instanceof XSLLocalParam) {
                if (((XSLLocalParam) param).isRequiredParam()) {
                    hasRequiredParams = true;
                }
            } else {
                hasContent = true;
            }
        }

        if (visibility == Visibility.ABSTRACT && hasContent) {
            compileError("A template with visibility='abstract' must have no body");
        }

    }

    @Override
    public void validateSubtree(ComponentDeclaration decl, boolean excludeStylesheet) throws XPathException {
        if (!isDeferredCompilation(getCompilation())) {
            super.validateSubtree(decl, excludeStylesheet);
        } else {
            try {
                validate(decl);
            } catch (XPathException err) {
                compileError(err);
            }
        }
    }

    @Override
    public void index(ComponentDeclaration decl, PrincipalStylesheetModule top) throws XPathException {
        if (getTemplateName() != null) {
            if (compiledNamedTemplate == null) {
                compiledNamedTemplate = new NamedTemplate(getTemplateName(), getConfiguration());
            }
            top.indexNamedTemplate(decl);
        }

    }

    /**
     * Mark tail-recursive calls on templates and functions.
     */

    @Override
    protected boolean markTailCalls() {
        StyleElement last = getLastChildInstruction();
        return last != null && last.markTailCalls();
    }

    /**
     * Compile: creates the executable form of the template
     */

    @Override
    public void compileDeclaration(Compilation compilation, ComponentDeclaration decl) throws XPathException {
        if (isDeferredCompilation(compilation)) {
            createSkeletonTemplate(compilation, decl);
            //System.err.println("Deferred - " + ++lazy);
            return;
        }
        if (compilation.getCompilerInfo().getOptimizerOptions().isSet(OptimizerOptions.TAIL_CALLS)) {
            isTailRecursive = markTailCalls();
        }
        Expression body = compileSequenceConstructor(compilation, decl, true);
        body.restoreParentPointers();
        RetainedStaticContext rsc = makeRetainedStaticContext();
        if (body.getRetainedStaticContext() == null) {
            body.setRetainedStaticContext(rsc); // bug 2608
        }
        if (match != null && compilation.getConfiguration().getBooleanProperty(Feature.STRICT_STREAMABILITY) &&
                isWithinDeclaredStreamableConstruct()) {
            checkStrictStreamability(body);
        }
        if (getTemplateName() != null) {
            compileNamedTemplate(compilation, body, decl);
        }
        if (match != null) {
            //System.err.println("Rules compiled - " + ++eager);
            compileTemplateRule(compilation, body, decl);
        }
    }

    private void checkStrictStreamability(Expression body) throws XPathException {
        getConfiguration().checkStrictStreamability(this, body);
    }

    private void compileNamedTemplate(Compilation compilation, Expression body, ComponentDeclaration decl) throws XPathException {
        RetainedStaticContext rsc = body.getRetainedStaticContext();
        compiledNamedTemplate.setPackageData(rsc.getPackageData());
        compiledNamedTemplate.setBody(body);
        compiledNamedTemplate.setStackFrameMap(stackFrameMap);
        compiledNamedTemplate.setSystemId(getSystemId());
        compiledNamedTemplate.setLineNumber(getLineNumber());
        compiledNamedTemplate.setColumnNumber(getColumnNumber());
        compiledNamedTemplate.setHasRequiredParams(hasRequiredParams);
        compiledNamedTemplate.setRequiredType(requiredType);
        compiledNamedTemplate.setContextItemRequirements(requiredContextItemType, mayOmitContextItem, absentFocus);
        compiledNamedTemplate.setRetainedStaticContext(rsc);
        compiledNamedTemplate.setDeclaredVisibility(getDeclaredVisibility());
        Component overridden = getOverriddenComponent();
        if (overridden != null) {
            checkCompatibility(overridden);
        }

        ContextItemStaticInfo cisi = getConfiguration().makeContextItemStaticInfo(requiredContextItemType, mayOmitContextItem);
        Expression body2 = refineTemplateBody(body, cisi);
        compiledNamedTemplate.setBody(body2);

        if (getCompilation().getCompilerInfo().getCodeInjector() != null) {
            getCompilation().getCompilerInfo().getCodeInjector().process(compiledNamedTemplate);
        }
    }




    private Expression refineTemplateBody(Expression body, ContextItemStaticInfo cisi) {
        Expression old = body;
        try {
            body = body.simplify();
        } catch (XPathException e) {
            if (e.isReportableStatically()) {
                compileError(e);
            } else {
                body = new ErrorExpression(new XmlProcessingException(e));
                ExpressionTool.copyLocationInfo(old, body);
            }
        }

        Configuration config = getConfiguration();
        if (visibility != Visibility.ABSTRACT) {
            try {
                if (requiredType != null && requiredType != SequenceType.ANY_SEQUENCE) {
                    Supplier role = () ->
                            new RoleDiagnostic(RoleDiagnostic.TEMPLATE_RESULT, diagnosticId, 0, "XTTE0505");
                    body = config.getTypeChecker(false).staticTypeCheck(body, requiredType, role, makeExpressionVisitor());
                }
            } catch (XPathException err) {
                if (err.isReportableStatically()) {
                    compileError(err);
                }
                body = new ErrorExpression(new XmlProcessingException(err));
                ExpressionTool.copyLocationInfo(old, body);
            }
        }

        try {
            ExpressionVisitor visitor = makeExpressionVisitor();
            body = body.typeCheck(visitor, cisi);
        } catch (XPathException e) {
            compileError(e);
        }

        return body;
    }

    public void compileTemplateRule(Compilation compilation, Expression body, ComponentDeclaration decl) {

        Configuration config = getConfiguration();

        if (getTemplateName() != null) {
            body = body.copy(new RebindingMap());
        }

        ItemType contextItemType;
        ContextItemStaticInfo cisi;
        // the template can't be called by name, so the context item must match the match pattern
        contextItemType = match.getItemType();
        if (contextItemType.equals(ErrorType.getInstance())) {
            // if the match pattern can't match anything, we produce a warning, not a hard error
            contextItemType = AnyItemType.getInstance();
        }
        cisi = config.makeContextItemStaticInfo(contextItemType, mayOmitContextItem);
        body = refineTemplateBody(body, cisi);

        boolean first = true;
        for (Map.Entry kvp : compiledTemplateRules.entrySet()) {
            if (!kvp.getKey().equals(Mode.OMNI_MODE)) {
                TemplateRule rule = kvp.getValue();
                if (first) {
                    rule.setMatchPattern(match);
                    rule.setBody(body);
                    if (compilation.getCompilerInfo().getCodeInjector() != null) {
                        compilation.getCompilerInfo().getCodeInjector().process(rule);
                        body = rule.getBody();
                    }
                    first = false;
                } else {
                    if (rule.getBody() == null) {
                        body = body.copy(new RebindingMap());
                    } else {
                        body = rule.getBody();
                    }
                }
                setCompiledTemplateRuleProperties(rule, body);
                rule.updateSlaveCopies();
            }
        }
        // following code needed only for diagnostics
        //body.verifyParentPointers();
    }

    private void createSkeletonTemplate(Compilation compilation, ComponentDeclaration decl) throws XPathException {
        StructuredQName[] modes = modeNames;
        if (isOmniMode()) {
            List all = new ArrayList<>();
            all.add(Mode.UNNAMED_MODE_NAME);
            RuleManager mgr = getCompilation().getPrincipalStylesheetModule().getRuleManager();
            for (Mode m : mgr.getAllNamedModes()) {
                all.add(m.getModeName());
            }
            modes = all.toArray(new StructuredQName[0]);
        }
        for (StructuredQName modeName : modes) {
            TemplateRule templateRule = compiledTemplateRules.get(modeName);
            if (templateRule == null) {
                templateRule = getConfiguration().makeTemplateRule();
            }
            templateRule.prepareInitializer(compilation, decl, modeName);
            compiledTemplateRules.put(modeName, templateRule);
            RetainedStaticContext rsc = makeRetainedStaticContext();
            templateRule.setPackageData(rsc.getPackageData());
            setCompiledTemplateRuleProperties(templateRule, null);
        }
    }

    private void setCompiledTemplateRuleProperties(TemplateRule templateRule, Expression body) {
        templateRule.setMatchPattern(match);
        templateRule.setBody(body);
        templateRule.setStackFrameMap(stackFrameMap);
        templateRule.setSystemId(getSystemId());
        templateRule.setLineNumber(getLineNumber());
        templateRule.setColumnNumber(getColumnNumber());
        templateRule.setHasRequiredParams(hasRequiredParams);
        templateRule.setRequiredType(requiredType);
        templateRule.setContextItemRequirements(requiredContextItemType, absentFocus);
    }

    /**
     * Code executed when the template is first executed under JIT. If the template is defined in several
     * modes, then this may be called several times, but it only does anything the first time. Mode-specific
     * processing is done in the TemplateRuleInitializer.
     *
     * @param compilation the compilation episode
     * @param decl        the template rule declaration
     * @throws XPathException if anything goes wrong
     */

    public synchronized void jitCompile(Compilation compilation, ComponentDeclaration decl) throws XPathException {
        if (!jitCompilationDone) {
            jitCompilationDone = true;
            compilation.setPreScan(false);
            processAllAttributes();
            checkForJitCompilationErrors(compilation);
            validateSubtree(decl, false);
            checkForJitCompilationErrors(compilation);
            compileDeclaration(compilation, decl);
            checkForJitCompilationErrors(compilation);
        }

    }

    private void checkForJitCompilationErrors(Compilation compilation) throws XPathException {
        if (compilation.getErrorCount() > 0) {
            XPathException e = new XPathException("Errors were reported during JIT compilation of template rule with match=\"" + matchAtt + "\"",
                                                  SaxonErrorCode.SXST0001, this);
            e.setHasBeenReported(true); // only intended as an exception message, not something to report to ErrorListener
            throw e;
        }
    }


    /**
     * Registers the template rule with each Mode that it belongs to.
     *
     * @param declaration Associates this template with a stylesheet module (in principle an xsl:template
     *                    element can be in a document that is imported more than once; these are separate declarations)
     * @throws XPathException if a failure occurs
     */

    public void register(ComponentDeclaration declaration) throws XPathException {
        if (match != null) {
            StylesheetModule module = declaration.getModule();
            RuleManager mgr = getCompilation().getPrincipalStylesheetModule().getRuleManager();
            ExpressionVisitor visitor = ExpressionVisitor.make(getStaticContext());
            for (StructuredQName modeName : getModeNames()) {
                Mode mode = mgr.obtainMode(modeName, false);
                if (mode == null) {
                    if (mgr.existsOmniMode()) {
                        Mode omniMode = mgr.obtainMode(Mode.OMNI_MODE, true);
                        mode = mgr.obtainMode(modeName, true);
                        SimpleMode.copyRules(omniMode.getActivePart(), mode.getActivePart());
                    } else {
                        mode = mgr.obtainMode(modeName, true);
                    }
                } else {
                    boolean ok = getPrincipalStylesheetModule().checkAcceptableModeForPackage(this, mode);
                    if (!ok) {
                        return;
                    }
                }
                Pattern match1 = match.copy(new RebindingMap());
                String typed = mode.getActivePart().getPropertyValue("typed");
                if ("strict".equals(typed) || "lax".equals(typed)) {
                    Pattern match2;
                    try {
                        match2 = match1.convertToTypedPattern(typed);
                    } catch (XPathException e) {
                        e.maybeSetLocation(this);
                        throw e;
                    }
                    if (match2 != match1) {
                        ContextItemStaticInfo info = getConfiguration().makeContextItemStaticInfo(AnyItemType.getInstance(), mayOmitContextItem);
                        ExpressionTool.copyLocationInfo(match, match2);
                        match2.setOriginalText(match.toString());
                        match2 = match2.typeCheck(visitor, info);
                        match1 = match2;
                    }
                    if (modeNames.length == 1) {
                        // If this is the only mode for the template, then we can use this enhanced match pattern
                        // for subsequent type-checking of the template body.
                        // TODO: we can now do this for all modes...
                        // TODO: but we need to take account of mode=#all, where modeNames.length==1
                        match = match2;
                    }
                }
                TemplateRule rule = compiledTemplateRules.get(modeName);
                if (rule == null) {
                    rule = getConfiguration().makeTemplateRule();
                    compiledTemplateRules.put(modeName, rule);
                }

                double prio = prioritySpecified ? priority : Double.NaN;
                mgr.registerRule(match1, rule, mode, module, prio, mgr.allocateSequenceNumber(), 0);

                if (mode.isDeclaredStreamable()) {
                    rule.setDeclaredStreamable(true);
                    if (!match1.isMotionless()) {
                        boolean fallback = getConfiguration().getBooleanProperty(Feature.STREAMING_FALLBACK);
                        String message = "Template rule is declared streamable but the match pattern is not motionless";
                        if (fallback) {
                            message += "\n  * Falling back to non-streaming implementation";
                            getStaticContext().issueWarning(message, SaxonErrorCode.SXWN9024, this);
                            rule.setDeclaredStreamable(false);
                            getCompilation().setFallbackToNonStreaming(true);
                        } else {
                            throw new XPathException(message, "XTSE3430", this);
                        }
                    }
                }

                if (mode.getDefaultResultType() != null && !declaresRequiredType) {
                    rule.setRequiredType(mode.getDefaultResultType());
                }

                // if adding a rule to the omniMode (mode='all') add it to all
                // the other modes as well. For all but the first, it needs to
                // be copied because the external component bindings might
                // differ from one mode to another.

                if (mode.getModeName().equals(Mode.OMNI_MODE)) {
                    compiledTemplateRules.put(Mode.UNNAMED_MODE_NAME, rule);
                    mgr.registerRule(match1, rule, mgr.getUnnamedMode(), module, prio, mgr.allocateSequenceNumber(), 0);
                    for (Mode m : mgr.getAllNamedModes()) {
                        if (m instanceof SimpleMode) {
                            TemplateRule ruleCopy = rule.copy();
                            if (m.isDeclaredStreamable()) {
                                ruleCopy.setDeclaredStreamable(true);
                            }
                            compiledTemplateRules.put(m.getModeName(), ruleCopy);
                            mgr.registerRule(match1.copy(new RebindingMap()),
                                             ruleCopy, m, module, prio, mgr.allocateSequenceNumber(), 0);
                        }
                    }
                }
            }
        }
    }

    /**
     * Allocate slot numbers to any local variables declared within a predicate within the match pattern
     */

    public void allocatePatternSlotNumbers() {
        if (match != null) {
            for (TemplateRule templateRule : compiledTemplateRules.values()) {
                for (Rule r : templateRule.getRules()) {
                    // In the case of a union pattern, allocate slots separately for each branch
                    Pattern match = r.getPattern();
                    // first slot in pattern is reserved for current()
                    int nextFree = 0;
                    if ((match.getDependencies() & StaticProperty.DEPENDS_ON_CURRENT_ITEM) != 0) {
                        nextFree = 1;
                    }
                    int slots = match.allocateSlots(getSlotManager(), nextFree);
                    // if the pattern calls user-defined functions, allocate at least one slot,
                    // to force a new context to be created for evaluating patterns (bug 3706)
                    if (slots == 0 && ((match.getDependencies() & StaticProperty.DEPENDS_ON_USER_FUNCTIONS) != 0)) {
                        slots = 1;
                    }
                    if (slots > 0) {
                        RuleManager mgr = getCompilation().getPrincipalStylesheetModule().getRuleManager();
                        boolean appliesToAll = false;
                        for (StructuredQName nc : modeNames) {
                            if (nc.equals(Mode.OMNI_MODE)) {
                                appliesToAll = true;
                                break;
                            }
                            Mode mode = mgr.obtainMode(nc, true);
                            mode.getActivePart().allocatePatternSlots(slots);
                        }
                        if (appliesToAll) {
                            for (Mode m : mgr.getAllNamedModes()) {
                                m.getActivePart().allocatePatternSlots(slots);
                            }
                            mgr.getUnnamedMode().getActivePart().allocatePatternSlots(slots);
                        }
                    }

                }
            }
        }
    }


    /**
     * This method is a bit of a misnomer, because it does more than invoke optimization of the template body.
     * In particular, it also registers the template rule with each Mode that it belongs to.
     *
     * @param declaration Associates this template with a stylesheet module (in principle an xsl:template
     *                    element can be in a document that is imported more than once; these are separate declarations)
     * @throws XPathException if errors are found
     */

    @Override
    public void optimize(ComponentDeclaration declaration) throws XPathException {
        Configuration config = getConfiguration();
        if (compiledNamedTemplate != null) {
            Expression body = compiledNamedTemplate.getBody();
            ContextItemStaticInfo cisi = getConfiguration().makeContextItemStaticInfo(requiredContextItemType, mayOmitContextItem);

            ExpressionVisitor visitor = makeExpressionVisitor();
            body = body.typeCheck(visitor, cisi);
            body = ExpressionTool.optimizeComponentBody(body, getCompilation(), visitor, cisi, true);
            compiledNamedTemplate.setBody(body);

            allocateLocalSlots(body);
            if (explaining) {
                Logger err = getConfiguration().getLogger();
                err.info("Optimized expression tree for named template at line " +
                                 getLineNumber() + " in " + getSystemId() + ':');
                body.explain(err);
            }
            body.restoreParentPointers();
        }
        if (match != null) {
            ItemType contextItemType = getContextItemTypeForTemplateRule();
            ContextItemStaticInfo cisi = config.makeContextItemStaticInfo(contextItemType, mayOmitContextItem);
            cisi.setContextPostureStriding();
            ExpressionVisitor visitor = makeExpressionVisitor();
            match.resetLocalStaticProperties();
            match = match.optimize(visitor, cisi);

            if (!isDeferredCompilation(getCompilation())) {
                Optimizer opt = getConfiguration().obtainOptimizer();
                try {
                    for (Map.Entry entry : compiledTemplateRules.entrySet()) {
                        if (!entry.getKey().equals(Mode.OMNI_MODE)) {
                            TemplateRule compiledTemplateRule = entry.getValue();
                            Expression templateRuleBody = compiledTemplateRule.getBody();
                            visitor.setOptimizeForStreaming(compiledTemplateRule.isDeclaredStreamable());
                            templateRuleBody = templateRuleBody.typeCheck(visitor, cisi);
                            templateRuleBody = ExpressionTool.optimizeComponentBody(templateRuleBody, getCompilation(), visitor, cisi, true);
                            compiledTemplateRule.setBody(templateRuleBody);
                            opt.checkStreamability(this, compiledTemplateRule);
                            allocateLocalSlots(templateRuleBody);
                            for (Rule r : compiledTemplateRule.getRules()) {
                                Pattern match = r.getPattern();
                                ContextItemStaticInfo info = getConfiguration().makeContextItemStaticInfo(match.getItemType(), mayOmitContextItem);
                                info.setContextPostureStriding();
                                Pattern m2 = match.optimize(visitor, info);
                                if (compiledTemplateRules.size() > 1) {
                                    m2 = m2.copy(new RebindingMap());
                                }
                                if (m2 != match) {
                                    r.setPattern(m2);
                                }
                            }

                            if (explaining) {
                                Logger err = getConfiguration().getLogger();
                                err.info("Optimized expression tree for template rule at line " +
                                                 getLineNumber() + " in " + getSystemId() + ':');
                                templateRuleBody.explain(err);
                            }
                        }
                    }
                } catch (XPathException e) {
                    e.maybeSetLocation(this);
                    compileError(e);
                }

            }
        }

    }

    public ItemType getContextItemTypeForTemplateRule() throws XPathException {
        Configuration config = getConfiguration();
        ItemType contextItemType = match.getItemType();
        if (contextItemType.equals(ErrorType.getInstance())) {
            // if the match pattern can't match anything, we produce a warning, not a hard error
            contextItemType = AnyItemType.getInstance();
        }
        if (requiredContextItemType != AnyItemType.getInstance()) {
            Affinity rel = config.getTypeHierarchy().relationship(contextItemType, requiredContextItemType);
            switch (rel) {
                case DISJOINT:
                    XPathException e = new XPathException("The declared context item type is inconsistent with the match pattern", "XPTY0004", this);
                    e.setIsTypeError(true);
                    throw e;
                case SUBSUMED_BY:
                case OVERLAPS:
                case SAME_TYPE:
                    // no action
                    break;
                case SUBSUMES:
                    contextItemType = requiredContextItemType;
                    break;
            }
        }
        return contextItemType;
    }



    /**
     * Get associated Procedure (for details of stack frame)
     */

    @Override
    public SlotManager getSlotManager() {
        return stackFrameMap;
    }


    /**
     * Get the compiled template
     *
     * @return the compiled template
     */

    public NamedTemplate getCompiledNamedTemplate() {
        return compiledNamedTemplate;
    }


    public Pattern getMatch() {
        return match;
    }

    public Map getTemplateRulesByMode() {
        return compiledTemplateRules;
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy