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) 2018-2022 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.instruct.ValueOf;
import net.sf.saxon.expr.parser.RoleDiagnostic;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.s9api.XmlProcessingError;
import net.sf.saxon.str.UnicodeString;
import net.sf.saxon.trans.Visibility;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.trans.XmlProcessingException;
import net.sf.saxon.transpile.CSharpSimpleEnum;
import net.sf.saxon.tree.iter.AxisIterator;
import net.sf.saxon.type.Affinity;
import net.sf.saxon.type.ItemType;
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.EnumSet;
import java.util.List;

import static net.sf.saxon.style.SourceBinding.BindingProperty.SELECT;


/**
 * Helper class for xsl:variable and xsl:param elements. 
*/ public class SourceBinding { private final StyleElement sourceElement; private StructuredQName name; private Expression select = null; private SequenceType declaredType = null; private SequenceType inferredType = null; protected SlotManager slotManager = null; // used only for global variable declarations private Visibility visibility; private GroundedValue constantValue = null; //private int properties; @CSharpSimpleEnum(flags=true) public enum BindingProperty { PRIVATE, GLOBAL, PARAM, TUNNEL, REQUIRED, IMPLICITLY_REQUIRED, ASSIGNABLE, SELECT, AS, DISALLOWS_CONTENT, STATIC, VISIBILITY, IMPLICITLY_DECLARED} @SuppressWarnings("FieldMayBeFinal") private EnumSet properties = EnumSet.noneOf(BindingProperty.class); // List of VariableReference objects that reference this XSLVariableDeclaration private final List references = new ArrayList<>(10); public SourceBinding(StyleElement sourceElement) { this.sourceElement = sourceElement; } public void prepareAttributes(EnumSet permittedAttributes) { AttributeMap atts = sourceElement.attributes(); AttributeInfo selectAtt = null; String asAtt = null; String extraAsAtt = null; String requiredAtt = null; String tunnelAtt = null; String assignableAtt = null; String staticAtt = null; String visibilityAtt = null; for (AttributeInfo att : atts) { NodeName attName = att.getNodeName(); String f = attName.getDisplayName(); if (f.equals("name")) { if (name == null || name.equals(errorName())) { processVariableName(att.getValue()); } } else if (f.equals("select")) { if (permittedAttributes.contains(SELECT)) { selectAtt = att; } else { sourceElement.compileErrorInAttribute( "The select attribute is not permitted on a function parameter", "XTSE0760", "select"); } } else if (f.equals("as") && permittedAttributes.contains(BindingProperty.AS)) { asAtt = att.getValue(); } else if (f.equals("required") && permittedAttributes.contains(BindingProperty.REQUIRED)) { requiredAtt = Whitespace.trim(att.getValue()); } else if (f.equals("tunnel")) { tunnelAtt = Whitespace.trim(att.getValue()); } else if (f.equals("static") && permittedAttributes.contains(BindingProperty.STATIC)) { staticAtt = Whitespace.trim(att.getValue()); } else if (f.equals("visibility") && permittedAttributes.contains(BindingProperty.VISIBILITY)) { visibilityAtt = Whitespace.trim(att.getValue()); } else if (NamespaceConstant.SAXON.equals(attName.getURI())) { if (sourceElement.isExtensionAttributeAllowed(attName.getDisplayName())) { if (attName.getLocalPart().equals("assignable") && permittedAttributes.contains(BindingProperty.ASSIGNABLE)) { assignableAtt = Whitespace.trim(att.getValue()); } else if (attName.getLocalPart().equals("as")) { extraAsAtt = att.getValue(); } else { sourceElement.checkUnknownAttribute(att.getNodeName()); } } } else { sourceElement.checkUnknownAttribute(att.getNodeName()); } } if (name == null) { sourceElement.reportAbsence("name"); name = errorName(); } if (selectAtt != null) { select = sourceElement.makeExpression(selectAtt.getValue(), selectAtt); } if (requiredAtt != null) { boolean required = sourceElement.processBooleanAttribute("required", requiredAtt); setProperty(BindingProperty.REQUIRED, required); if (required && select != null) { sourceElement.compileError("xsl:param: cannot supply a default value when required='yes'"); } } if (tunnelAtt != null) { boolean tunnel = sourceElement.processBooleanAttribute("tunnel", tunnelAtt); if (tunnel && !permittedAttributes.contains(BindingProperty.TUNNEL)) { sourceElement.compileErrorInAttribute("The only permitted value of the 'tunnel' attribute is 'no'", "XTSE0020", "tunnel"); } setProperty(BindingProperty.TUNNEL, tunnel); } if (assignableAtt != null) { boolean assignable = sourceElement.processBooleanAttribute("saxon:assignable", assignableAtt); setProperty(BindingProperty.ASSIGNABLE, assignable); } if (staticAtt != null) { boolean statick = sourceElement.processBooleanAttribute("static", staticAtt); setProperty(BindingProperty.STATIC, statick); if (statick) { setProperty(BindingProperty.DISALLOWS_CONTENT, true); } if (statick && !hasProperty(BindingProperty.GLOBAL)) { sourceElement.compileErrorInAttribute("Only global declarations can be static", "XTSE0020", "static"); } } declaredType = combineTypeDeclarations(asAtt, extraAsAtt); if (visibilityAtt != null) { if (hasProperty(BindingProperty.PARAM)) { sourceElement.compileErrorInAttribute("The visibility attribute is not allowed on xsl:param", "XTSE0020", "visibility"); } else { visibility = sourceElement.interpretVisibilityValue(visibilityAtt, ""); } if (!hasProperty(BindingProperty.GLOBAL)) { sourceElement.compileErrorInAttribute("The visibility attribute is allowed only on global declarations", "XTSE0020", "visibility"); } } if (hasProperty(BindingProperty.STATIC) && visibility != Visibility.PRIVATE && visibilityAtt != null) { sourceElement.compileErrorInAttribute("A static variable or parameter must be private", "XTSE0020", "static"); } } /** * This method is called to establish basic signature information for local parameters of a named template, * so that this can be checked against xsl:call-template instructions. It is called while a named template * is being indexed: see bug 3722. */ public void prepareTemplateSignatureAttributes() { AttributeMap atts = sourceElement.attributes(); String asAtt = null; String extraAsAtt = null; for (AttributeInfo att : atts) { NodeName attName = att.getNodeName(); String f = attName.getDisplayName(); if (f.equals("name")) { if (name == null || name.equals(errorName())) { processVariableName(att.getValue()); } } else if (f.equals("as")) { asAtt = att.getValue(); } else if (f.equals("required")) { String requiredAtt = Whitespace.trim(att.getValue()); boolean required = sourceElement.processBooleanAttribute("required", requiredAtt); setProperty(BindingProperty.REQUIRED, required); } else if (f.equals("tunnel")) { String tunnelAtt = Whitespace.trim(att.getValue()); boolean tunnel = sourceElement.processBooleanAttribute("tunnel", tunnelAtt); setProperty(BindingProperty.TUNNEL, tunnel); } else if (NamespaceConstant.SAXON.equals(attName.getURI())) { if (attName.getLocalPart().equals("as")) { extraAsAtt = att.getValue(); } } } if (name == null) { sourceElement.reportAbsence("name"); name = errorName(); } declaredType = combineTypeDeclarations(asAtt, extraAsAtt); } private SequenceType combineTypeDeclarations(String asAtt, String extraAsAtt) { SequenceType declaredType = null; if (asAtt != null) { try { declaredType = sourceElement.makeSequenceType(asAtt); } catch (XPathException e) { sourceElement.compileErrorInAttribute(e.getMessage(), e.getErrorCodeLocalPart(), "as"); } } if (extraAsAtt != null) { SequenceType extraResultType = null; try { extraResultType = sourceElement.makeExtendedSequenceType(extraAsAtt); } catch (XPathException e) { sourceElement.compileErrorInAttribute(e.getMessage(), e.getErrorCodeLocalPart(), "saxon:as"); extraResultType = SequenceType.ANY_SEQUENCE; } if (asAtt != null) { Affinity rel = sourceElement.getConfiguration().getTypeHierarchy().sequenceTypeRelationship(extraResultType, declaredType); if (rel == Affinity.SAME_TYPE || rel == Affinity.SUBSUMED_BY) { declaredType = extraResultType; } else { sourceElement.compileErrorInAttribute("When both are present, @saxon:as must be a subtype of @as", "SXER7TBA", "as"); } } else { declaredType = extraResultType; } } return declaredType; } /** * 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 */ private void processVariableName(String nameAttribute) { if (nameAttribute != null) { if (nameAttribute.startsWith("$")) { sourceElement.compileErrorInAttribute("Invalid variable name (no '$' sign needed)", "XTSE0020", "name"); nameAttribute = nameAttribute.substring(1); } name = sourceElement.makeQName(nameAttribute, null, "name"); } } /** * 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(BindingProperty.DISALLOWS_CONTENT) && sourceElement.hasChildNodes()) { if (isStatic()) { sourceElement.compileError("A static variable or parameter must have no content", "XTSE0010"); } else { sourceElement.compileError("Within xsl:function, an xsl:param element 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(BindingProperty.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(BindingProperty.REQUIRED)) { if (Cardinality.allowsZero(declaredType.getCardinality())) { select = Literal.makeEmptySequence(); select.setRetainedStaticContext(sourceElement.makeRetainedStaticContext()); } else { // The implicit default value () is not valid for the required type, so // it is treated as if there is no default setProperty(BindingProperty.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"); } } } } } select = sourceElement.typeCheck("select", select); } public boolean isStatic() { return hasProperty(BindingProperty.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 */ public void checkAgainstRequiredType(SequenceType required) { 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 || sourceElement instanceof XSLGlobalParam) { category = RoleDiagnostic.PARAM; errorCode = "XTTE0590"; } RoleDiagnostic role = new RoleDiagnostic(category, name.getDisplayName(), 0); //role.setSourceLocator(new ExpressionLocation(this)); role.setErrorCode(errorCode); select = sourceElement.getConfiguration().getTypeChecker(false).staticTypeCheck(select, required, 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(new XmlProcessingException(err)); } } } /** * Get the name of the variable * * @return the variable's name */ public StructuredQName getVariableQName() { if (name == null) { processVariableName(sourceElement.getAttributeValue("", "name")); } return name; } /** * Set a boolean property of the variable * * @param prop the property to be set (e.g. {@link BindingProperty#TUNNEL}) * @param flag true to set the property on, false to set it off */ public void setProperty(BindingProperty prop, boolean flag) { if (flag) { properties.add(prop); } else { properties.remove(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(BindingProperty prop) { return properties.contains(prop); } /** * 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()); UnicodeString constant = null; // bug 3748 if (textonly && b instanceof ValueOf && ((ValueOf) b).getSelect() instanceof StringLiteral) { constant = ((StringLiteral) ((ValueOf) b).getSelect()).getString(); } DocumentInstr doc = new DocumentInstr(textonly, constant); 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 = sourceElement.getConfiguration().getTypeChecker(false).staticTypeCheck( select, declaredType, role, sourceElement.makeExpressionVisitor()); } catch (XPathException err) { err.setLocator(sourceElement); XmlProcessingError error = new XmlProcessingException(err); sourceElement.compileError(error); select = new ErrorExpression(error); } } } } /** * 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 = sourceElement.getVisibility(); if (hasProperty(BindingProperty.PARAM) || hasProperty(BindingProperty.ASSIGNABLE) || !(visibility == Visibility.PRIVATE || visibility == Visibility.FINAL)) { SequenceType declared = getDeclaredType(); return inferredType = declared == null ? SequenceType.ANY_SEQUENCE : declared; } 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); } public GroundedValue getConstantValue() { if (constantValue == null) { final SequenceType type = getInferredType(true); final TypeHierarchy th = sourceElement.getConfiguration().getTypeHierarchy(); if (!hasProperty(BindingProperty.ASSIGNABLE) && !hasProperty(BindingProperty.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 Affinity relation = th.relationship(select.getItemType(), type.getPrimaryType()); if (relation == Affinity.SAME_TYPE || relation == Affinity.SUBSUMED_BY) { constantValue = ((Literal) select).getGroundedValue(); } } } } return constantValue; } /** * Notify all references to this variable of the data type * * @param compiledGlobalVariable null if this is a local variable; otherwise, the compiled global variable */ public void fixupReferences(GlobalVariable compiledGlobalVariable) { final SequenceType type = getInferredType(true); //GroundedValue constantValue = null; int properties = 0; if (!hasProperty(BindingProperty.ASSIGNABLE) && !hasProperty(BindingProperty.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, getConstantValue(), 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 - 2024 Weber Informatics LLC | Privacy Policy