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

net.sf.saxon.style.SourceBinding Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 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.expr.*;
import net.sf.saxon.expr.instruct.DocumentInstr;
import net.sf.saxon.expr.instruct.GlobalVariable;
import net.sf.saxon.expr.instruct.SlotManager;
import net.sf.saxon.expr.parser.RoleDiagnostic;
import net.sf.saxon.expr.parser.TypeChecker;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.trans.Visibility;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.AttributeLocation;
import net.sf.saxon.tree.iter.AxisIterator;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.Type;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.type.UType;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.StringValue;
import net.sf.saxon.value.Whitespace;

import java.util.ArrayList;
import java.util.List;


/**
 * Helper class for xsl:variable and xsl:param elements. 
*/ public class SourceBinding { private StyleElement sourceElement; private StructuredQName name; private Expression select = null; private SequenceType declaredType = null; private SequenceType inferredType = null; protected String constantText = null; protected SlotManager slotManager = null; // used only for global variable declarations private Visibility visibility; private int properties; public static final int PRIVATE = 1; public static final int GLOBAL = 2; public static final int PARAM = 4; public static final int TUNNEL = 8; public static final int REQUIRED = 16; public static final int IMPLICITLY_REQUIRED = 32; public static final int ASSIGNABLE = 64; public static final int SELECT = 128; public static final int AS = 256; public static final int DISALLOWS_CONTENT = 512; public static final int STATIC = 2048; public static final int VISIBILITY = 4096; public static final int IMPLICITLY_DECLARED = 8192; // List of VariableReference objects that reference this XSLVariableDeclaration private List references = new ArrayList(10); public SourceBinding(StyleElement sourceElement) { this.sourceElement = sourceElement; } public void prepareAttributes(int permittedAttributes) throws XPathException { AttributeCollection atts = sourceElement.getAttributeList(); String selectAtt = null; String asAtt = null; String requiredAtt = null; String tunnelAtt = null; String assignableAtt = null; String staticAtt = null; String visibilityAtt = null; for (int a = 0; a < atts.getLength(); a++) { String f = atts.getQName(a); if (f.equals("name")) { if (name == null || name.equals(errorName())) { processVariableName(atts.getValue(a)); } } else if (f.equals("select")) { if ((permittedAttributes & SELECT) != 0) { selectAtt = atts.getValue(a); } else { sourceElement.compileErrorInAttribute( "The select attribute is not permitted on a function parameter", "XTSE0760", "select"); } } else if (f.equals("as") && ((permittedAttributes & AS) != 0)) { asAtt = atts.getValue(a); } else if (f.equals("required") && ((permittedAttributes & REQUIRED) != 0)) { requiredAtt = Whitespace.trim(atts.getValue(a)); } else if (f.equals("tunnel")) { tunnelAtt = Whitespace.trim(atts.getValue(a)); } else if (f.equals("static") && ((permittedAttributes & STATIC) != 0)) { staticAtt = Whitespace.trim(atts.getValue(a)); } else if (f.equals("visibility") && ((permittedAttributes & VISIBILITY) != 0)) { visibilityAtt = Whitespace.trim(atts.getValue(a)); } else if (atts.getLocalName(a).equals("assignable") && NamespaceConstant.SAXON.equals(atts.getURI(a)) && ((permittedAttributes & ASSIGNABLE) != 0)) { assignableAtt = Whitespace.trim(atts.getValue(a)); } else { sourceElement.checkUnknownAttribute(atts.getNodeName(a)); } } if (name == null) { sourceElement.reportAbsence("name"); name = errorName(); } if (selectAtt != null) { select = sourceElement.makeExpression(selectAtt, sourceElement.getAttributeList().getIndex("", "select")); } if (requiredAtt != null) { boolean required = sourceElement.processBooleanAttribute("required", requiredAtt); setProperty(REQUIRED, required); } if (tunnelAtt != null) { boolean tunnel = sourceElement.processBooleanAttribute("tunnel", tunnelAtt); if (tunnel && ((permittedAttributes & TUNNEL) == 0)){ sourceElement.compileErrorInAttribute("The only permitted value of the 'tunnel' attribute is 'no'", "XTSE0020", "tunnel"); } setProperty(TUNNEL, tunnel); } if (assignableAtt != null) { boolean assignable = sourceElement.processBooleanAttribute("saxon:assignable", assignableAtt); setProperty(ASSIGNABLE, assignable); } if (staticAtt != null) { if (sourceElement.isXslt30Processor()) { boolean statick = sourceElement.processBooleanAttribute("static", staticAtt); setProperty(STATIC, statick); if (statick) { setProperty(SourceBinding.DISALLOWS_CONTENT, true); } } else if (!sourceElement.forwardsCompatibleModeIsEnabled()) { sourceElement.compileErrorInAttribute("The attribute 'static' is not allowed", "XTSE0020", "static"); } } if (asAtt != null) { declaredType = sourceElement.makeSequenceType(asAtt); } if (visibilityAtt != null) { if (hasProperty(PARAM) && sourceElement.isXslt30Processor()) { sourceElement.compileErrorInAttribute("The visibility attribute is not allowed on xsl:param", "XTSE0020", "visibility"); } else { sourceElement.check30attribute("visibility"); visibility = sourceElement.interpretVisibilityValue(visibilityAtt, ""); // TODO: rules concerning the interaction of @visibility and @static } } } /** * Get the declaration in the stylesheet * * @return the node in the stylesheet containing this variable binding */ public StyleElement getSourceElement() { return sourceElement; } /** * Set the name of the variable * * @param name the name of the variable as a QName */ public void setVariableQName(StructuredQName name) { this.name = name; } /** * Set the declared type of the variable * @param declaredType the declared type */ public void setDeclaredType(SequenceType declaredType) { this.declaredType = declaredType; } /** * Process the QName of the variable. Validate the name and place it in the "name" field; * if invalid, construct an error message and place a dummy name in the "name" field for * recovery purposes. * * @param nameAttribute the lexical QName * @throws XPathException if the name is invalid */ private void processVariableName(String nameAttribute) throws XPathException { try { if (nameAttribute != null) { name = sourceElement.makeQName(nameAttribute); } } catch (NamespaceException err) { name = errorName(); StructuredQName n = StructuredQName.fromClarkName("name"); throw new XPathException("Invalid variable name: " + err.getMessage(), "XTSE0020", new AttributeLocation(sourceElement, n)); } catch (XPathException err) { name = errorName(); StructuredQName n = StructuredQName.fromClarkName("name"); throw new XPathException("Invalid variable name: " + err.getMessage() + (nameAttribute.startsWith("$") ? " (No '$' sign needed)" : ""), "XTSE0020", new AttributeLocation(sourceElement, n)); } } /** * Fallback name to be returned if there is anything wrong with the variable's real name * (after reporting an error) * * @return a fallback name */ private StructuredQName errorName() { return new StructuredQName("saxon", NamespaceConstant.SAXON, "error-variable-name"); } /** * Validate the declaration * @throws XPathException if the declaration is invalid */ public void validate() throws XPathException { if (select != null && sourceElement.hasChildNodes()) { sourceElement.compileError("An " + sourceElement.getDisplayName() + " element with a select attribute must be empty", "XTSE0620"); } if (hasProperty(DISALLOWS_CONTENT) && sourceElement.hasChildNodes()) { sourceElement.compileError("The xsl:param child of xsl:function must have no content", "XTSE0620"); } if (visibility == Visibility.ABSTRACT && (select != null || sourceElement.hasChildNodes())) { sourceElement.compileError("An abstract variable must have no select attribute and no content", "XTSE0620"); } } /** * Hook to allow additional validation of a parent element immediately after its * children have been validated. * @throws XPathException if the declaration is invalid */ public void postValidate() throws XPathException { checkAgainstRequiredType(declaredType); if (select == null && !hasProperty(DISALLOWS_CONTENT) && visibility != Visibility.ABSTRACT) { AxisIterator kids = sourceElement.iterateAxis(AxisInfo.CHILD); NodeInfo first = kids.next(); if (first == null) { if (declaredType == null) { select = new StringLiteral(StringValue.EMPTY_STRING); select.setRetainedStaticContext(sourceElement.makeRetainedStaticContext()); } else { if (sourceElement instanceof XSLLocalParam || sourceElement instanceof XSLGlobalParam) { if (!hasProperty(REQUIRED)) { if (Cardinality.allowsZero(declaredType.getCardinality())) { select = Literal.makeEmptySequence(); } else { // The implicit default value () is not valid for the required type, so // it is treated as if there is no default setProperty(IMPLICITLY_REQUIRED, true); } } } else { if (Cardinality.allowsZero(declaredType.getCardinality())) { select = Literal.makeEmptySequence(); select.setRetainedStaticContext(sourceElement.makeRetainedStaticContext()); } else { sourceElement.compileError("The implicit value () is not valid for the declared type", "XTTE0570"); } } } } else { if (kids.next() == null) { // there is exactly one child node if (first.getNodeKind() == Type.TEXT) { // it is a text node: optimize for this case constantText = first.getStringValue(); } } } } select = sourceElement.typeCheck("select", select); } public boolean isStatic() { return hasProperty(STATIC); } /** * Check the supplied select expression against the required type. * * @param required The type required by the variable declaration, or in the case * of xsl:with-param, the signature of the called template * @throws net.sf.saxon.trans.XPathException * if an error is detected */ public void checkAgainstRequiredType(SequenceType required) throws XPathException { if (visibility != Visibility.ABSTRACT) { try { if (required != null) { // check that the expression is consistent with the required type if (select != null) { int category = RoleDiagnostic.VARIABLE; String errorCode = "XTTE0570"; if (sourceElement instanceof XSLLocalParam) { category = RoleDiagnostic.PARAM; errorCode = "XTTE0600"; } else if (sourceElement instanceof XSLWithParam) { category = RoleDiagnostic.PARAM; errorCode = "XTTE0590"; } RoleDiagnostic role = new RoleDiagnostic(category, name.getDisplayName(), 0); //role.setSourceLocator(new ExpressionLocation(this)); role.setErrorCode(errorCode); select = TypeChecker.staticTypeCheck(select, required, false, role, sourceElement.makeExpressionVisitor()); } else { // do the check later } } } catch (XPathException err) { err.setLocator(sourceElement); // because the expression wasn't yet linked into the module sourceElement.compileError(err); select = new ErrorExpression(err); } } } /** * Get the name of the variable * * @return the variable's name */ public StructuredQName getVariableQName() { if (name == null) { try { processVariableName(sourceElement.getAttributeValue("", "name")); } catch (XPathException e) { return errorName(); } } return name; } /** * Set a boolean property of the variable * * @param prop the property to be set (e.g. {@link #TUNNEL}) * @param flag true to set the property on, false to set it off */ public void setProperty(int prop, boolean flag) { if (flag) { properties |= prop; } else { properties &= ~prop; } } /** * Get a boolean property of the variable * * @param prop the property whose value is required * @return true if the variable has the specified property, otherwise false */ public boolean hasProperty(int prop) { return (properties & prop) != 0; } /** * Get all the known references to this variable * * @return the list of references */ public List getReferences() { return references; } /** * Get the SlotManager associated with this stylesheet construct. The SlotManager contains the * information needed to manage the local stack frames used by run-time instances of the code. * * @return the associated SlotManager object */ public SlotManager getSlotManager() { return slotManager; } /** * If the element contains a sequence constructor, convert this to an expression and assign it to the * select attribute * * @param compilation the compilation episode * @param decl the declaration being compiled * @throws XPathException if a static error is found, for example a type error */ public void handleSequenceConstructor(Compilation compilation, ComponentDeclaration decl) throws XPathException { // handle the "temporary tree" case by creating a Document sub-instruction // to construct and return a document node. if (sourceElement.hasChildNodes()) { if (declaredType == null) { Expression b = sourceElement.compileSequenceConstructor(compilation, decl, true); if (b == null) { b = Literal.makeEmptySequence(); } boolean textonly = UType.TEXT.subsumes(b.getItemType().getUType()); DocumentInstr doc = new DocumentInstr(textonly, constantText); doc.setContentExpression(b); doc.setRetainedStaticContext(sourceElement.makeRetainedStaticContext()); select = doc; } else { select = sourceElement.compileSequenceConstructor(compilation, decl, true); if (select == null) { select = Literal.makeEmptySequence(); } try { RoleDiagnostic role = new RoleDiagnostic(RoleDiagnostic.VARIABLE, name.getDisplayName(), 0); role.setErrorCode("XTTE0570"); select = select.simplify(); select = TypeChecker.staticTypeCheck(select, declaredType, false, role, sourceElement.makeExpressionVisitor()); } catch (XPathException err) { err.setLocator(sourceElement); sourceElement.compileError(err); select = new ErrorExpression(err); } } } } /** * Get the type actually declared for the attribute * * @return the type appearing in the "as" attribute, or null if the attribute was absent */ public SequenceType getDeclaredType() { if (declaredType == null) { // may be handling a forwards reference - see hof-038 String asAtt = sourceElement.getAttributeValue("", "as"); if (asAtt == null) { return null; } else { try { declaredType = sourceElement.makeSequenceType(asAtt); } catch (XPathException err) { // the error will be reported when we get round to processing the function declaration } } } return declaredType; } /** * Get the select expression actually appearing in the variable declaration * * @return the select expression as it appears, or null if it is absent */ public Expression getSelectExpression() { return select; } /** * Get the best available static type of the variable. * * @param useContentRules set to true if the standard rules for xsl:variable and similar elements apply, * whereby the element's contained sequence constructor substitutes for the select attribute * @return the static type declared for the variable, or inferred from its initialization */ public SequenceType getInferredType(boolean useContentRules) { if (inferredType != null) { return inferredType; } Visibility visibility; try { visibility = sourceElement.getVisibility(); } catch (XPathException err) { visibility = Visibility.PUBLIC; // error will be caught some time } if (hasProperty(PARAM) || hasProperty(ASSIGNABLE) || !(visibility == Visibility.PRIVATE || visibility == Visibility.FINAL)) { return inferredType = declaredType == null ? SequenceType.ANY_SEQUENCE : declaredType; } if (select != null) { TypeHierarchy th = sourceElement.getConfiguration().getTypeHierarchy(); if (Literal.isEmptySequence(select)) { // returning Type.EMPTY gives problems with static type checking return inferredType = declaredType == null ? SequenceType.ANY_SEQUENCE : declaredType; } ItemType actual = select.getItemType(); int card = select.getCardinality(); if (declaredType != null) { if (!th.isSubType(actual, declaredType.getPrimaryType())) { actual = declaredType.getPrimaryType(); } if (!Cardinality.subsumes(declaredType.getCardinality(), card)) { card = declaredType.getCardinality(); } } inferredType = SequenceType.makeSequenceType(actual, card); return inferredType; } if (useContentRules) { if (sourceElement.hasChildNodes()) { if (declaredType == null) { return SequenceType.makeSequenceType(NodeKindTest.DOCUMENT, StaticProperty.EXACTLY_ONE); } else { return declaredType; } } else { if (declaredType == null) { // no select attribute or content: value is an empty string return SequenceType.SINGLE_STRING; } else { return declaredType; } } } return declaredType; } /** * Method called by VariableReference to register the variable reference for * subsequent fixup * * @param ref the variable reference being registered */ public void registerReference(BindingReference ref) { references.add(ref); } /** * Notify all references to this variable of the data type * @param compiledGlobalVariable null if this is a local variable; otherwise, the compiled global variable * @throws XPathException if the declaration is invalid */ public void fixupReferences(GlobalVariable compiledGlobalVariable) throws XPathException { final SequenceType type = getInferredType(true); final TypeHierarchy th = sourceElement.getConfiguration().getTypeHierarchy(); GroundedValue constantValue = null; int properties = 0; if (!hasProperty(ASSIGNABLE) && !hasProperty(PARAM) && !(visibility == Visibility.PUBLIC || visibility == Visibility.ABSTRACT)) { if (select instanceof Literal) { // we can't rely on the constant value because it hasn't yet been type-checked, // which could change it (eg by numeric promotion). Rather than attempt all the type-checking // now, we do a quick check. See test bug64 int relation = th.relationship(select.getItemType(), type.getPrimaryType()); if (relation == TypeHierarchy.SAME_TYPE || relation == TypeHierarchy.SUBSUMED_BY) { constantValue = ((Literal) select).getValue(); } } if (select != null) { properties = select.getSpecialProperties(); } } for (BindingReference reference : references) { if (compiledGlobalVariable != null) { reference.fixup(compiledGlobalVariable); } reference.setStaticType(type, constantValue, properties); } } /** * Notify all variable references of the Binding instruction * * @param binding the Binding that represents this variable declaration in the executable code tree */ protected void fixupBinding(Binding binding) { for (BindingReference reference : references) { reference.fixup(binding); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy