net.sf.saxon.style.StyleElement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.Configuration;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.expr.sort.SortKeyDefinition;
import net.sf.saxon.expr.sort.SortKeyDefinitionList;
import net.sf.saxon.functions.Current;
import net.sf.saxon.functions.registry.VendorFunctionSetHE;
import net.sf.saxon.lib.Feature;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.lib.StringCollator;
import net.sf.saxon.lib.Validation;
import net.sf.saxon.ma.arrays.ArrayFunctionSet;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.*;
import net.sf.saxon.s9api.HostLanguage;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.s9api.XmlProcessingError;
import net.sf.saxon.trans.*;
import net.sf.saxon.transpile.CSharpInjectMembers;
import net.sf.saxon.transpile.CSharpSimpleEnum;
import net.sf.saxon.tree.AttributeLocation;
import net.sf.saxon.tree.iter.AxisIterator;
import net.sf.saxon.tree.iter.NodeListIterator;
import net.sf.saxon.tree.linked.ElementImpl;
import net.sf.saxon.tree.linked.NodeImpl;
import net.sf.saxon.tree.linked.TextImpl;
import net.sf.saxon.tree.util.Navigator;
import net.sf.saxon.type.*;
import net.sf.saxon.value.BigDecimalValue;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.Whitespace;
import javax.xml.transform.SourceLocator;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* Abstract superclass for all element nodes in the stylesheet.
* Note: this class implements Locator. The element retains information about its own location
* in the stylesheet, which is useful when an XSLT static error is found.
*/
// Enable the C# code to accept a lambda expression in calls to children()
@CSharpInjectMembers(code = {""
+ " protected System.Collections.Generic.IEnumerable children(System.Predicate filter) {"
+ " return new Saxon.Hej.tree.util.Navigator.ChildrenAsIterable(this, Saxon.Hej.pattern.NodeSelector.of(filter));"
+ " }"})
public abstract class StyleElement extends ElementImpl {
/*@Nullable*/ protected String[] extensionNamespaces = null; // a list of URIs
private String[] excludedNamespaces = null; // a list of URIs
protected int version = -1; // the effective version of this element
protected ExpressionContext staticContext = null;
protected XmlProcessingIncident validationError = null;
protected OnFailure reportingCircumstances = OnFailure.REPORT_ALWAYS;
protected String defaultXPathNamespace = null;
protected String defaultCollationName = null;
protected StructuredQName defaultMode;
protected boolean expandText = false;
private StructuredQName objectName; // for instructions that define an XSLT named object, the name of that object
private String baseURI;
private Compilation compilation;
private Loc savedLocation = null;
private int defaultValidation = Validation.DEFAULT;
// Conditions under which an error is to be reported
@CSharpSimpleEnum
public enum OnFailure {
REPORT_ALWAYS, REPORT_UNLESS_FORWARDS_COMPATIBLE, REPORT_IF_INSTANTIATED,
REPORT_STATICALLY_UNLESS_FALLBACK_AVAILABLE, REPORT_DYNAMICALLY_UNLESS_FALLBACK_AVAILABLE,
IGNORED_INSTRUCTION
}
protected int actionsCompleted = 0;
public static final int ACTION_VALIDATE = 1;
public static final int ACTION_COMPILE = 2;
public static final int ACTION_TYPECHECK = 4;
public static final int ACTION_OPTIMIZE = 8;
public static final int ACTION_FIXUP = 16;
public static final int ACTION_PROCESS_ATTRIBUTES = 32;
/**
* Constructor
*/
public StyleElement() {
}
public Compilation getCompilation() {
return compilation;
}
public void setCompilation(Compilation compilation) {
this.compilation = compilation;
}
public StylesheetPackage getPackageData() {
return getPrincipalStylesheetModule().getStylesheetPackage();
}
@Override
public Configuration getConfiguration() {
return compilation.getConfiguration();
}
/**
* Get the static context for expressions on this element
*
* @return the static context
*/
public ExpressionContext getStaticContext() {
if (staticContext == null) {
staticContext = new ExpressionContext(this, null);
}
return staticContext;
}
public ExpressionContext getStaticContext(StructuredQName attributeName) {
return new ExpressionContext(this, attributeName);
}
/**
* Get the base URI of the element, which acts as the static base URI for XPath expressions defined
* on this element. This is an expensive operation so the result is cached
*
* @return the base URI
*/
@Override
public String getBaseURI() {
if (baseURI == null) {
baseURI = super.getBaseURI();
}
return baseURI;
}
/**
* Make an expression visitor
*
* @return the expression visitor
*/
public ExpressionVisitor makeExpressionVisitor() {
return ExpressionVisitor.make(getStaticContext());
}
/**
* Ask whether the code is compiled in schema-aware mode
*
* @return true if the compilation is schema-aware
*/
public boolean isSchemaAware() {
return getCompilation().isSchemaAware();
}
/**
* Make this node a substitute for a temporary one previously added to the tree. See
* StyleNodeFactory for details. "A node like the other one in all things but its class".
* Note that at this stage, the node will not yet be known to its parent, though it will
* contain a reference to its parent; and it will have no children.
*
* @param temp the element which this one is substituting for
*/
public void substituteFor(StyleElement temp) {
setRawParent(temp.getRawParent());
setAttributes(temp.attributes());
//setNamespaceList(temp.getNamespaceList());
setNamespaceMap(temp.getAllNamespaces());
setNodeName(temp.getNodeName());
setRawSequenceNumber(temp.getRawSequenceNumber());
extensionNamespaces = temp.extensionNamespaces;
excludedNamespaces = temp.excludedNamespaces;
version = temp.version;
staticContext = temp.staticContext;
validationError = temp.validationError;
reportingCircumstances = temp.reportingCircumstances;
compilation = temp.compilation;
//lineNumber = temp.lineNumber;
}
/**
* Set a validation error. This is an error detected during construction of this element on the
* stylesheet, but which is not to be reported until later.
*
* @param reason the details of the error
* @param circumstances a code identifying the circumstances under which the error is to be reported
*/
public void setValidationError(XmlProcessingIncident reason,
OnFailure circumstances) {
validationError = reason;
reportingCircumstances = circumstances;
}
void setIgnoreInstruction() {
reportingCircumstances = OnFailure.IGNORED_INSTRUCTION;
}
/**
* Ask whether this node is an instruction. The default implementation says it isn't.
*
* @return true if this element is an instruction
*/
public boolean isInstruction() {
return false;
}
/**
* Ask whether this node is a declaration, that is, a permitted child of xsl:stylesheet
* (including xsl:include and xsl:import). The default implementation returns false
*
* @return true if the element is a permitted child of xsl:stylesheet or xsl:transform
*/
public boolean isDeclaration() {
return false;
}
/**
* Get the visibility of the component. Returns the actual value of the visibility attribute,
* after validation, unless this is absent, in which case it returns the default value of PRIVATE.
* Invokes {@link #invalidAttribute(String, String)} if the value is invalid.
*
* @return the declared visibility of the component, or {@link Visibility#PRIVATE}
* if the visibility attribute is absent.
*/
public Visibility getVisibility() {
String vis = getAttributeValue("", "visibility");
if (vis == null) {
return Visibility.PRIVATE;
} else {
return interpretVisibilityValue(vis, "");
}
}
/**
* Get the visibility of the component. Returns the actual value of the visibility attribute,
* after validation, unless this is absent, in which case it returns null.
*
* @return the declared visibility of the component, or null if the visibility attribute is absent.
*/
public Visibility getDeclaredVisibility() {
String vis = getAttributeValue("", "visibility");
if (vis == null) {
return Visibility.UNDEFINED;
} else {
return interpretVisibilityValue(vis, "");
}
}
/**
* Mark tail-recursive calls on templates and functions.
* For most instructions, this returns false.
*
* @return true if one or more tail calls were identified
*/
protected boolean markTailCalls() {
return false;
}
/**
* Determine whether this type of element is allowed to contain a sequence constructor
*
* @return true if this instruction is allowed to contain a sequence constructor
*/
protected boolean mayContainSequenceConstructor() {
return false;
}
/**
* Determine whether this type of element is allowed to contain an xsl:fallback
* instruction. Note that this is only relevant if the element is an instruction.
*
* @return true if this element is allowed to contain an xsl:fallback
*/
protected boolean mayContainFallback() {
return mayContainSequenceConstructor();
}
/**
* Determine whether this type of element is allowed to contain an xsl:param element
*
* @return true if this element is allowed to contain an xsl:param
*/
protected boolean mayContainParam() {
return false;
}
/**
* Get the effective value of the default-validation attribute
*
* @return the value of default-validation, as a constant from the {@link Validation} class,
* or Validation.STRIP if there is no containing element with a default-validation
* attribute.
*/
int getDefaultValidation() {
int v = defaultValidation;
NodeInfo p = this;
while (v == Validation.DEFAULT) {
p = p.getParent();
if (!(p instanceof StyleElement)) {
return Validation.STRIP;
//return getCompilation().isSchemaAware() ? Validation.PRESERVE : Validation.STRIP;
}
v = ((StyleElement) p).defaultValidation;
}
return v;
}
/**
* Make a structured QName, using this Element as the context for namespace resolution.
* If the name is unprefixed, the default namespace is not used. If the name is
* not valid, then a compileError is reported, and the value saxon:error-name is returned.
*
* @param lexicalQName The lexical QName as written, in the form "[prefix:]localname". Leading and trailing whitespace
* will be trimmed. The EQName syntax "Q{uri}local" is also
* accepted.
* @param errorCode The error code to be used if the QName is not valid. If this is set to null,
* then the code used is XTSE0280 for an undeclared prefix, and XTSE0020
* for all other errors. The code XTSE0080 is used if the URI is a reserved
* namespace, regardless of the supplied error code.
* @param attributeName EQName of the attribute containing the QName, for use in error reporting.
* May be null.
* @return the StructuredQName representation of this lexical QName
*/
public final StructuredQName makeQName(String lexicalQName, String errorCode, String attributeName) {
StructuredQName qName;
try {
qName = StructuredQName.fromLexicalQName((lexicalQName), false,
true, this);
} catch (XPathException e) {
e.setIsStaticError(true);
if (errorCode == null) {
String code = e.getErrorCodeLocalPart();
if ("FONS0004".equals(code)) {
e.setErrorCode("XTSE0280");
} else if ("FOCA0002".equals(code)) {
e.setErrorCode("XTSE0020");
} else if (code == null) {
e.setErrorCode("XTSE0020");
}
} else {
e.setErrorCode(errorCode);
}
if (attributeName == null) {
e.setLocator(this);
} else {
e.setLocator(new AttributeLocation(this, StructuredQName.fromEQName((attributeName))));
}
compileError(e);
qName = new StructuredQName("saxon", NamespaceConstant.SAXON, "error-name");
}
if (NamespaceConstant.isReserved(qName.getURI())) {
if (qName.hasURI(NamespaceConstant.XSLT)) {
if (qName.getLocalPart().equals("initial-template")
&& (this instanceof XSLTemplate || this instanceof XSLCallTemplate)) {
return qName;
}
if (qName.getLocalPart().equals("original")) {
// OK if within xsl:override
if (findAncestorElement(StandardNames.XSL_OVERRIDE) != null) {
return qName;
}
}
}
XmlProcessingIncident err = new XmlProcessingIncident("Namespace prefix " +
qName.getPrefix() + " refers to a reserved namespace", "XTSE0080");
compileError(err);
qName = new StructuredQName("saxon", NamespaceConstant.SAXON, "error-name");
}
return qName;
}
/**
* Get the first ancestor element in the stylesheet tree that has a given name, supplied by
* fingerprint.
*
* @param fingerprint the name of the required element
* @return the first (innermost) ancestor with the required name, or null if none is found
*/
StyleElement findAncestorElement(int fingerprint) {
NodeInfo parent = getParent();
while (true) {
if (parent instanceof StyleElement) {
if (parent.getFingerprint() == fingerprint) {
return (StyleElement) parent;
} else {
parent = parent.getParent();
}
} else {
return null;
}
}
}
/**
* Assuming this is an xsl:use-package element, find the package to which it refers.
*
* @return the package referenced by this xsl:use-package element; or null if this is not
* an xsl:use-package element
*/
public StylesheetPackage getUsedPackage() {
return null;
}
/**
* Check that a reference to xsl:original appears within an xsl:override element, and that
* the name of the containing component matches the name of a component in the used stylesheet
* package; return the component in that package with matching symbolic name
*
* @param componentKind the kind of component required, e.g. StandardNames.XSL_TEMPLATE
* @return the component with matching name in the used stylesheet
* @throws XPathException if the xsl:original reference appears in an invalid context
*/
public Actor getXslOriginal(int componentKind) throws XPathException {
StyleElement container = componentKind == getFingerprint() ? this : findAncestorElement(componentKind);
if (!(container instanceof StylesheetComponent)) {
throw new XPathException(
"A reference to xsl:original appears within the wrong kind of component: in this case" +
", it must be within xsl:" + getNamePool().getLocalName(componentKind), "XTSE0650", this);
}
SymbolicName originalName = ((StylesheetComponent) container).getSymbolicName();
StyleElement xslOverride = container.findAncestorElement(StandardNames.XSL_OVERRIDE);
if (xslOverride == null) {
throw new XPathException("A reference to xsl:original can be used only within an xsl:override element");
}
StyleElement usePackage = xslOverride.findAncestorElement(StandardNames.XSL_USE_PACKAGE);
if (usePackage == null) {
throw new XPathException("The parent of xsl:override must be an xsl:use-package element", "XTSE0010", xslOverride);
}
Component overridden = usePackage.getUsedPackage().getComponent(originalName);
if (overridden == null) {
// the error will be detected and reported elsewhere
return null;
}
return overridden.getActor();
}
/**
* Get the component that this declaration overrides, or null if this is not an overriding declaration
*
* @return the overridden component, or null
*/
Component getOverriddenComponent() {
if (!(this instanceof StylesheetComponent)) {
return null;
}
SymbolicName originalName = ((StylesheetComponent) this).getSymbolicName();
StyleElement xslOverride = findAncestorElement(StandardNames.XSL_OVERRIDE);
if (xslOverride == null) {
return null;
}
StyleElement usePackage = xslOverride.findAncestorElement(StandardNames.XSL_USE_PACKAGE);
if (usePackage == null) {
return null;
}
return usePackage.getUsedPackage().getComponent(originalName);
}
public RetainedStaticContext makeRetainedStaticContext() {
return getStaticContext().makeRetainedStaticContext();
}
/**
* Ask whether this instruction requires a different retained static context from the containing
* (parent) instruction. That is, this instruction changes the static base URI, the default collation,
* or the set of in-scope namespaces.
*
* @return true if the context for evaluating this instruction differs in relevant ways from that
* of the calling instruction
*/
boolean changesRetainedStaticContext() {
NodeImpl parent = getParent();
return parent == null
|| !ExpressionTool.equalOrNull(getBaseURI(), parent.getBaseURI())
|| defaultCollationName != null
|| defaultXPathNamespace != null
|| !(parent instanceof StyleElement)
|| getAllNamespaces() != parent.getAllNamespaces()
|| getEffectiveVersion() != ((StyleElement)parent).getEffectiveVersion();
}
/**
* Get the namespace context of the instruction.
*
* @return the namespace context. This method does not make a copy of the namespace context,
* so a reference to the returned NamespaceResolver will lock the stylesheet tree in memory.
*/
public NamespaceResolver getNamespaceResolver() {
return this;
}
/**
* Process the attributes of this element and all its children
*
* @throws XPathException in the event of a static error being detected
*/
public void processAllAttributes() throws XPathException {
processDefaultCollationAttribute();
processDefaultMode();
staticContext = new ExpressionContext(this, null);
processAttributes();
for (NodeInfo child : children()) {
if (child instanceof StyleElement) {
((StyleElement) child).processAllAttributes();
} else if (child instanceof TextValueTemplateNode) {
((TextValueTemplateNode) child).parse();
}
}
}
/**
* Process the standard attributes such as {@code [xsl:]expand-text}. Invokes
* {@link #compileError(String)} or similar if the value of any of these attributes
* is invalid.
*
* The method processes:
*
* - {@code extension-element-prefixes}
* - {@code exclude-result-prefixes}
* - {@code version}
* - {@code default-xpath-namespace}
* - {@code default-validation}
* - {@code expand-text}
*
*
* but not:
*
* - {@code default-collation}
* - {@code default-mode}
*
*
* @param namespace either "" to find the attributes in the null namespace,
* or NamespaceConstant.XSLT to find them in the XSLT namespace
*/
public void processStandardAttributes(String namespace) {
processExtensionElementAttribute(namespace);
processExcludedNamespaces(namespace);
processVersionAttribute(namespace);
processDefaultXPathNamespaceAttribute(namespace);
processDefaultValidationAttribute(namespace);
processExpandTextAttribute(namespace);
}
/**
* Get an attribute value given the Clark name of the attribute (that is,
* the name in {uri}local format).
*
* @param clarkName the name of the attribute in {uri}local format
* @return the value of the attribute if it exists, or null otherwise
*/
public String getAttributeValue(String clarkName) {
NodeName nn = FingerprintedQName.fromClarkName(clarkName);
return getAttributeValue(nn.getURI(), nn.getLocalPart());
}
/**
* Process the attribute list for the element. This is a wrapper method that calls
* prepareAttributes (provided in the subclass) and traps any exceptions
*/
protected final void processAttributes() {
prepareAttributes();
}
/**
* Check whether an unknown attribute is permitted.
*
* @param nc The name code of the attribute name
*/
protected void checkUnknownAttribute(NodeName nc) {
String attributeURI = nc.getURI();
String elementURI = getURI();
String clarkName = nc.getStructuredQName().getClarkName();
if (forwardsCompatibleModeIsEnabled()) {
// then unknown attributes are permitted and ignored
return;
}
// allow xsl:extension-element-prefixes etc on an extension element
if (isInstruction() &&
attributeURI.equals(NamespaceConstant.XSLT) &&
!elementURI.equals(NamespaceConstant.XSLT) &&
(clarkName.endsWith("}default-collation") ||
clarkName.endsWith("}default-mode") ||
clarkName.endsWith("}xpath-default-namespace") ||
clarkName.endsWith("}expand-text") ||
clarkName.endsWith("}extension-element-prefixes") ||
clarkName.endsWith("}exclude-result-prefixes") ||
clarkName.endsWith("}version") ||
clarkName.endsWith("}default-validation") ||
clarkName.endsWith("}use-when"))) {
return;
}
// allow standard attributes on an XSLT element
if (elementURI.equals(NamespaceConstant.XSLT) &&
(clarkName.equals("default-collation") ||
clarkName.equals("default-mode") ||
clarkName.equals("expand-text") ||
clarkName.equals("xpath-default-namespace") ||
clarkName.equals("extension-element-prefixes") ||
clarkName.equals("exclude-result-prefixes") ||
clarkName.equals("version") ||
clarkName.equals("default-validation") ||
clarkName.equals("use-when"))) {
return;
}
if ("".equals(attributeURI) || NamespaceConstant.XSLT.equals(attributeURI)) {
compileErrorInAttribute("Attribute " + Err.wrap(nc.getDisplayName(), Err.ATTRIBUTE) +
" is not allowed on element " + Err.wrap(getDisplayName(), Err.ELEMENT),
"XTSE0090", clarkName);
} else if (NamespaceConstant.SAXON.equals(attributeURI)) {
compileWarning("Unrecognized attribute in Saxon namespace: " + nc.getDisplayName(), "XTSE0090");
}
}
/**
* Set the attribute list for the element. This is called to process the attributes (note
* the distinction from processAttributes in the superclass).
* Must be supplied in a subclass
*/
protected abstract void prepareAttributes();
/**
* Find the last child instruction of this instruction. Returns null if
* there are no child instructions, or if the last child is a text node.
*
* @return the last child instruction, or null if there are no child instructions
*/
StyleElement getLastChildInstruction() {
StyleElement last = null;
for (NodeInfo child : children()) {
if (child instanceof StyleElement) {
last = (StyleElement) child;
} else {
last = null;
}
}
return last;
}
/**
* Compile an XPath expression in the context of this stylesheet element
*
* @param expression the source text of the XPath expression
* @param att the attribute containing the XPath expression, or
* null if the expression is in a text node
* @return the compiled expression tree for the XPath expression. In the case of an error,
* returns an ErrorExpression that will fail at run-time if executed.
*/
public Expression makeExpression(String expression, AttributeInfo att) {
try {
StaticContext env = staticContext;
if (att != null) {
StructuredQName attName = att.getNodeName().getStructuredQName();
env = getStaticContext(attName);
}
return ExpressionTool.make(expression, env, 0, Token.EOF,
getCompilation().getCompilerInfo().getCodeInjector());
} catch (XPathException err) {
err.maybeSetLocation(allocateLocation());
if (err.isReportableStatically()) {
compileError(err);
}
ErrorExpression erexp = new ErrorExpression(new XmlProcessingException(err));
erexp.setRetainedStaticContext(makeRetainedStaticContext());
erexp.setLocation(allocateLocation());
return erexp;
}
}
/**
* Make a pattern in the context of this stylesheet element
*
* @param pattern the source text of the pattern
* @return the compiled pattern
*/
Pattern makePattern(String pattern, String attributeName) {
try {
StaticContext env = getStaticContext(new StructuredQName("", "", attributeName));
Pattern p = Pattern.make(pattern, env, getCompilation().getPackageData());
p.setLocation(allocateLocation());
return p;
} catch (XPathException err) {
err.maybeSetErrorCode("XTSE0340");
if ("XPST0003".equals(err.getErrorCodeLocalPart())) {
err.setErrorCode("XTSE0340");
}
compileError(err);
NodeTestPattern nsp = new NodeTestPattern(AnyNodeTest.getInstance());
nsp.setLocation(allocateLocation());
return nsp;
}
}
/**
* Make an attribute value template in the context of this stylesheet element
*
* @param expression the source text of the attribute value template
* @param att the attribute containing the AVT, or null in the case of a text value template
* @return a compiled XPath expression that computes the value of the attribute (including
* concatenating the results of embedded expressions with any surrounding fixed text)
*/
protected Expression makeAttributeValueTemplate(String expression, AttributeInfo att) {
StaticContext env = att == null ?
staticContext :
getStaticContext(att.getNodeName().getStructuredQName());
if (att != null) {
StructuredQName attName = att.getNodeName().getStructuredQName();
env = getStaticContext(attName);
}
try {
return AttributeValueTemplate.make(expression, env);
} catch (XPathException err) {
compileError(err);
return new StringLiteral(expression);
}
}
/**
* Check the value of an attribute, as supplied statically
*
* @param name the name of the attribute
* @param value the value of the attribute
* @param avt set to true if the value is permitted to be an attribute value template
* @param allowed list of permitted values, which must be in alphabetical order
*/
void checkAttributeValue(String name, String value, boolean avt, String[] allowed) {
if (avt && value.contains("{")) {
return;
}
if (Arrays.binarySearch(allowed, value) < 0) {
StringBuilder sb = new StringBuilder(64);
sb.append("Invalid value for ");
sb.append("@");
sb.append(name);
sb.append(". Value must be one of (");
for (int i = 0; i < allowed.length; i++) {
sb.append(i == 0 ? "" : "|");
sb.append(allowed[i]);
}
sb.append(")");
compileError(sb.toString(), "XTSE0020");
}
}
public final static String[] YES_NO = {"0", "1", "false", "no", "true", "yes"};
/**
* Process an attribute whose value is yes, no, true, false, 1, or 0; returning true or false.
*
* @param name the name of the attribute (used for diagnostics)
* @param value the value of the attribute
* @return the value of the attribute as a boolean
*/
public boolean processBooleanAttribute(String name, String value) {
String s = Whitespace.trim(value);
if (isYes(s)) {
return true;
} else if (isNo(s)) {
return false;
} else {
invalidAttribute(name, "yes|no | true|false | 1|0");
return false; // never get here
}
}
/**
* Ask whether an attribute is "yes" or one of its accepted synonyms
* @param s the value to be tested. Whitespace should be trimmed by the caller
* @return true if the value is "yes", "true", or "1"
*/
public static boolean isYes(String s) {
return "yes".equals(s) || "true".equals(s) || "1".equals(s);
}
/**
* Ask whether an attribute is "no" or one of its accepted synonyms
*
* @param s the value to be tested. Whitespace should be trimmed by the caller
* @return true if the value is "no", "false", or "0"
*/
public static boolean isNo(String s) {
return "no".equals(s) || "false".equals(s) || "0".equals(s);
}
boolean processStreamableAtt(String streamableAtt) {
boolean streamable = processBooleanAttribute("streamable", streamableAtt);
if (streamable) {
if (!getConfiguration().isLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XSLT)) {
compileWarning("Request for streaming ignored: this Saxon configuration does not support streaming", SaxonErrorCode.SXST0068);
return false;
}
if ("off".equals(getConfiguration().getConfigurationProperty(Feature.STREAMABILITY))) {
compileWarning("Request for streaming ignored: streaming is disabled in this Saxon configuration", SaxonErrorCode.SXST0068);
return false;
}
}
return streamable;
}
/**
* Process an attribute whose value is a SequenceType
*
* @param sequenceType the source text of the attribute
* @return the processed sequence type
* @throws XPathException if the syntax is invalid or for example if it refers to a type
* that is not in the static context
*/
public SequenceType makeSequenceType(String sequenceType)
throws XPathException {
ExpressionContext env = getStaticContext();
int languageLevel = env.getXPathVersion();
if (languageLevel == 30) {
languageLevel = 305; // XPath 3.0 + XSLT extensions
}
XPathParser parser =
getConfiguration().newExpressionParser("XP", false, languageLevel);
QNameParser qp = new QNameParser(staticContext.getNamespaceResolver())
.withAcceptEQName(staticContext.getXPathVersion() >= 30)
.withErrorOnBadSyntax("XPST0003")
.withErrorOnUnresolvedPrefix("XPST0081");
parser.setQNameParser(qp);
return parser.parseSequenceType(sequenceType, staticContext);
}
SequenceType makeExtendedSequenceType(String sequenceType)
throws XPathException {
ExpressionContext env = getStaticContext(new StructuredQName("saxon", NamespaceConstant.SAXON, "as"));
XPathParser parser =
getConfiguration().newExpressionParser("XP", false, 40);
QNameParser qp = new QNameParser(staticContext.getNamespaceResolver())
.withAcceptEQName(true)
.withErrorOnBadSyntax("XPST0003")
.withErrorOnUnresolvedPrefix("XPST0081");
parser.setQNameParser(qp);
return parser.parseExtendedSequenceType(sequenceType, env);
}
/**
* Process the [xsl:]extension-element-prefixes attribute if there is one
*
* @param ns the namespace URI of the attribute - either the XSLT namespace or "" for the null namespace
*/
void processExtensionElementAttribute(String ns) {
String ext = getAttributeValue(ns, "extension-element-prefixes");
if (ext != null) {
// go round twice, once to count the values and next to add them to the array
int count = 0;
StringTokenizer st1 = new StringTokenizer(ext, " \t\n\r", false);
while (st1.hasMoreTokens()) {
st1.nextToken();
count++;
}
extensionNamespaces = new String[count];
count = 0;
StringTokenizer st2 = new StringTokenizer(ext, " \t\n\r", false);
while (st2.hasMoreTokens()) {
String s = st2.nextToken();
if ("#default".equals(s)) {
s = "";
}
String uri = getURIForPrefix(s, false);
if (uri == null) {
extensionNamespaces = null;
compileError("Namespace prefix " + s + " is undeclared", "XTSE1430");
} else if (NamespaceConstant.isReserved(uri)) {
compileError("Namespace " + uri + " is reserved: it cannot be used for extension instructions " +
"(perhaps exclude-result-prefixes was intended).",
"XTSE0085");
extensionNamespaces[count++] = uri;
} else {
extensionNamespaces[count++] = uri;
}
}
}
}
/**
* Process the [xsl:]exclude-result-prefixes attribute if there is one
*
* @param ns the namespace URI of the attribute required, either the XSLT namespace or ""
*/
void processExcludedNamespaces(String ns) {
String ext = getAttributeValue(ns, "exclude-result-prefixes");
if (ext != null) {
if ("#all".equals(Whitespace.trim(ext))) {
List excluded = new ArrayList<>();
for (NamespaceBinding binding : getAllNamespaces()) {
excluded.add(binding.getURI());
}
excludedNamespaces = excluded.toArray(new String[0]);
} else {
// go round twice, once to count the values and next to add them to the array
int count = 0;
StringTokenizer st1 = new StringTokenizer(ext, " \t\n\r", false);
while (st1.hasMoreTokens()) {
st1.nextToken();
count++;
}
excludedNamespaces = new String[count];
count = 0;
StringTokenizer st2 = new StringTokenizer(ext, " \t\n\r", false);
while (st2.hasMoreTokens()) {
String s = st2.nextToken();
if ("#default".equals(s)) {
s = "";
} else if ("#all".equals(s)) {
compileError("In exclude-result-prefixes, cannot mix #all with other values", "XTSE0020");
}
String uri = getURIForPrefix(s, true);
if (uri == null) {
excludedNamespaces = null;
compileError("Namespace prefix " + s + " is not declared", "XTSE0808");
break;
}
excludedNamespaces[count++] = uri;
if (s.isEmpty() && uri.isEmpty()) {
compileError("Cannot exclude the #default namespace when no default namespace is declared",
"XTSE0809");
}
}
}
}
}
/**
* Process the [xsl:]version attribute if there is one
*
* @param ns the namespace URI of the attribute required, either the XSLT namespace or ""
*/
protected void processVersionAttribute(String ns) {
String v = Whitespace.trim(getAttributeValue(ns, "version"));
if (v != null) {
ConversionResult val = BigDecimalValue.makeDecimalValue(v, true);
if (val instanceof ValidationFailure) {
version = 30;
compileError("The version attribute must be a decimal literal", "XTSE0110");
} else {
// Note this will normalize the decimal so that trailing spaces are not significant
version = ((BigDecimalValue) val).getDecimalValue().multiply(BigDecimal.TEN).intValue();
if (version < 20 && version != 10) {
// XSLT 2.0 says use backwards compatible mode. XSLT 3.0 says we can raise an error.
// Both allow a warning
issueWarning("Unrecognized version " + val + ": treated as 1.0", this);
version = 10;
} else if (version > 20 && version < 30) {
issueWarning("Unrecognized version " + val + ": treated as 2.0", this);
version = 20;
}
}
}
}
/**
* Get the numeric value of the version number appearing as an attribute on this element,
* or inherited from its ancestors
*
* @return the version number times ten as an integer
*/
int getEffectiveVersion() {
if (version == -1) {
NodeInfo node = getParent();
if (node instanceof StyleElement) {
version = ((StyleElement) node).getEffectiveVersion();
} else {
return 20; // defensive programming
}
}
return version;
}
/**
* Validate the value of the [xsl:]validation attribute
*
* @param value the raw value of the attribute
* @return the encoded value of the attribute
*/
protected int validateValidationAttribute(String value) {
int code = Validation.getCode(value);
if (code == Validation.INVALID) {
String prefix = this instanceof LiteralResultElement ? "xsl:" : "";
compileError("Invalid value of " + prefix + "validation attribute: '" + value + "'", "XTSE0020");
code = getDefaultValidation();
}
if (!isSchemaAware()) {
if (code == Validation.STRICT) {
compileError("To perform validation, a schema-aware XSLT processor is needed", "XTSE1660");
}
code = Validation.STRIP;
}
return code;
}
/**
* Ask if an extension attribute is allowed; if no Professional Edition license is available,
* issue a warning saying the attribute is ignored, and return false
* @param attribute the name of the attribute
* @return true if the extension attribute is allowed, false if not
*/
protected boolean isExtensionAttributeAllowed(String attribute) {
if (getConfiguration().isLicensedFeature(Configuration.LicenseFeature.PROFESSIONAL_EDITION)) {
return true;
} else {
issueWarning("The option " + getDisplayName() + "/@" + attribute + " is ignored because it requires a Saxon-PE license", this);
return false;
}
}
/**
* Determine whether forwards-compatible mode is enabled for this element
*
* @return true if forwards-compatible mode is enabled
*/
boolean forwardsCompatibleModeIsEnabled() {
return getEffectiveVersion() > 30;
}
/**
* Determine whether 1.0-compatible mode is enabled for this element
*
* @return true if 1.0 compatable mode is enabled, that is, if this or an enclosing
* element specifies an [xsl:]version attribute whose value is less than 2.0
*/
boolean xPath10ModeIsEnabled() {
return getEffectiveVersion() < 20;
}
/**
* Process the [xsl:]default-collation attribute if there is one.
*/
void processDefaultCollationAttribute() {
String ns = getURI().equals(NamespaceConstant.XSLT) ? "" : NamespaceConstant.XSLT;
String v = getAttributeValue(ns, "default-collation");
StringBuilder reasons = new StringBuilder();
if (v != null) {
StringTokenizer st = new StringTokenizer(v, " \t\n\r", false);
while (st.hasMoreTokens()) {
String uri = st.nextToken();
if (uri.equals(NamespaceConstant.CODEPOINT_COLLATION_URI)) {
defaultCollationName = uri;
return;
} else {
URI collationURI;
try {
collationURI = new URI(uri);
if (!collationURI.isAbsolute()) {
URI base = new URI(getBaseURI());
collationURI = base.resolve(collationURI);
uri = collationURI.toString();
}
} catch (URISyntaxException err) {
compileError("default collation '" + uri + "' is not a valid URI");
uri = NamespaceConstant.CODEPOINT_COLLATION_URI;
}
try {
if (getConfiguration().getCollation(uri) != null) {
defaultCollationName = uri;
return;
} else {
if (reasons.length() != 0) {
reasons.append("; ");
}
reasons.append("Collation ").append(uri).append(" is not recognized");
}
} catch (XPathException e) {
if (reasons.length() != 0) {
reasons.append("; ");
}
reasons.append("Collation ").append(uri).append(" is not recognized (").append(e.getMessage()).append(")");
// Ignore an unrecognized collation URI
}
}
// if not recognized, try the next URI in order
}
String msg = "No recognized collation URI found in default-collation attribute";
if (reasons.length() != 0) {
msg += ". ";
msg += reasons.toString();
}
compileErrorInAttribute(msg, "XTSE0125",
new StructuredQName("", ns, "default-collation").getClarkName());
}
}
/**
* Get the default collation for this stylesheet element. If no default collation is
* specified in the stylesheet, return the Unicode codepoint collation name.
*
* @return the name of the default collation
*/
protected String getDefaultCollationName() {
StyleElement e = this;
while (true) {
if (e.defaultCollationName != null) {
return e.defaultCollationName;
}
NodeInfo p = e.getParent();
if (!(p instanceof StyleElement)) {
break;
}
e = (StyleElement) p;
}
return getConfiguration().getDefaultCollationName();
}
/**
* Find a named collation. Note this method should only be used at compile-time, before declarations
* have been pre-processed. After that time, use getCollation().
*
* @param name identifies the name of the collation required
* @param baseURI the base URI to be used for resolving the collation name if it is relative
* @return null if the collation is not found
* @throws XPathException if either URI is invalid as a URI
*/
StringCollator findCollation(String name, String baseURI) throws XPathException {
return getConfiguration().getCollation(name, baseURI);
}
/**
* Process the [xsl:]default-mode attribute if there is one
*/
void processDefaultMode() {
String ns = getURI().equals(NamespaceConstant.XSLT) ? "" : NamespaceConstant.XSLT;
String v = getAttributeValue(ns, "default-mode");
if (v != null) {
if (v.equals("#unnamed")) {
defaultMode = Mode.UNNAMED_MODE_NAME;
} else {
defaultMode = makeQName(v, null, "default-mode");
}
}
PrincipalStylesheetModule psm = compilation.getPrincipalStylesheetModule();
final StructuredQName checkedName = defaultMode;
if (psm != null && psm.isDeclaredModes()) {
// It will be null on the xsl:package element itself
psm.addFixupAction(() -> {
if (psm.getRuleManager().obtainMode(checkedName, false) == null) {
XPathException err = new XPathException("Mode " + checkedName.getDisplayName() + " is not declared in an xsl:mode declaration", "XTSE3085");
err.setLocation(this);
throw err;
}
});
}
}
/**
* Get the default mode for this stylesheet element.
*
* @return the name of the default mode, obtained by looking for the default-mode attribute on this element
* and all all its ancestors. In the absence of a default-mode attribute, returns the magic value
* {@link Mode#UNNAMED_MODE_NAME}
*/
StructuredQName getDefaultMode() throws XPathException {
if (defaultMode == null) {
processDefaultMode();
if (defaultMode == null) {
NodeInfo p = getParent();
if (p instanceof StyleElement) {
return defaultMode = ((StyleElement) p).getDefaultMode();
} else {
return defaultMode = Mode.UNNAMED_MODE_NAME;
}
}
}
return defaultMode;
}
/**
* Check whether a particular extension element namespace is defined on this node.
* This checks this node only, not the ancestor nodes.
* The implementation checks whether the prefix is included in the
* [xsl:]extension-element-prefixes attribute.
*
* @param uri the namespace URI being tested
* @return true if this namespace is defined on this element as an extension element namespace
*/
private boolean definesExtensionElement(String uri) {
if (extensionNamespaces == null) {
return false;
}
for (String extensionNamespace : extensionNamespaces) {
if (extensionNamespace.equals(uri)) {
return true;
}
}
return false;
}
/**
* Check whether a namespace uri defines an extension element. This checks whether the
* namespace is defined as an extension namespace on this or any ancestor node.
*
* @param uri the namespace URI being tested
* @return true if the URI is an extension element namespace URI
*/
public boolean isExtensionNamespace(String uri) {
NodeInfo anc = this;
while (anc instanceof StyleElement) {
if (((StyleElement) anc).definesExtensionElement(uri)) {
return true;
}
anc = anc.getParent();
}
return false;
}
/**
* Check whether this node excludes a particular namespace from the result.
* This method checks this node only, not the ancestor nodes.
*
* @param uri the namespace URI being tested
* @return true if the namespace is excluded by virtue of an [xsl:]exclude-result-prefixes attribute
*/
private boolean definesExcludedNamespace(String uri) {
if (excludedNamespaces == null) {
return false;
}
for (String excludedNamespace : excludedNamespaces) {
if (excludedNamespace.equals(uri)) {
return true;
}
}
return false;
}
/**
* Check whether a namespace uri defines an namespace excluded from the result.
* This checks whether the namespace is defined as an excluded namespace on this
* or any ancestor node.
*
* @param uri the namespace URI being tested
* @return true if this namespace URI is a namespace excluded by virtue of exclude-result-prefixes
* on this element or on an ancestor element
*/
boolean isExcludedNamespace(String uri) {
if (uri.equals(NamespaceConstant.XSLT) || uri.equals(NamespaceConstant.XML)) {
return true;
}
if (isExtensionNamespace(uri)) {
return true;
}
NodeInfo anc = this;
while (anc instanceof StyleElement) {
if (((StyleElement) anc).definesExcludedNamespace(uri)) {
return true;
}
anc = anc.getParent();
}
return false;
}
/**
* Process the [xsl:]xpath-default-namespace attribute if there is one
*
* @param ns the namespace URI of the attribute required (the default namespace or the XSLT namespace.)
*/
void processDefaultXPathNamespaceAttribute(String ns) {
String v = getAttributeValue(ns, "xpath-default-namespace");
if (v != null) {
defaultXPathNamespace = v;
}
}
/**
* Get the default XPath namespace for elements and types
*
* @return the default namespace for elements and types.
* Return {@link NamespaceConstant#NULL} for the non-namespace
*/
public String getDefaultXPathNamespace() {
NodeInfo anc = this;
while (anc instanceof StyleElement) {
String x = ((StyleElement) anc).defaultXPathNamespace;
if (x != null) {
return x;
}
anc = anc.getParent();
}
return compilation.getCompilerInfo().getDefaultElementNamespace();
}
/**
* Process the [xsl:]expand-text attribute if there is one (and if XSLT 3.0 is enabled)
*
* @param ns the namespace URI of the attribute required (the default namespace or the XSLT namespace.)
*/
void processExpandTextAttribute(String ns) {
String v = getAttributeValue(ns, "expand-text");
if (v != null) {
expandText = processBooleanAttribute("expand-text", v);
} else {
NodeInfo parent = getParent();
expandText = parent instanceof StyleElement && ((StyleElement) parent).expandText;
}
}
/**
* Process the [xsl:]expand-text attribute if there is one
*
* @param ns the namespace URI of the attribute required (the default namespace or the XSLT namespace.)
*/
void processDefaultValidationAttribute(String ns) {
String v = getAttributeValue(ns, "default-validation");
if (v != null) {
int val = Validation.getCode(v);
if (val == Validation.STRIP || val == Validation.PRESERVE) {
defaultValidation = val;
} else {
compileErrorInAttribute("@default-validation must be preserve|strip", "XTSE0020", "default-validation");
}
}
}
/**
* Ask whether content value templates are available within this element
*
* @return true if content value templates are enabled
*/
boolean isExpandingText() {
return expandText;
}
/**
* Get the Schema type definition for a type named in the stylesheet (in a
* "type" attribute).
*
* @param typeAtt the value of the type attribute
* @return the corresponding schema type
*/
public SchemaType getSchemaType(String typeAtt) {
try {
String uri;
String lname;
if (typeAtt.startsWith("Q{")) {
StructuredQName q = makeQName(typeAtt, "XTSE1520", "type");
uri = q.getURI();
lname = q.getLocalPart();
} else {
String[] parts = NameChecker.getQNameParts(typeAtt);
lname = parts[1];
if ("".equals(parts[0])) {
// Name is unprefixed: use the default-xpath-namespace
uri = getDefaultXPathNamespace();
} else {
uri = getURIForPrefix(parts[0], false);
if (uri == null) {
compileError("Namespace prefix for type annotation is undeclared", "XTSE1520");
return null;
}
}
}
if (uri.equals(NamespaceConstant.SCHEMA)) {
SchemaType t = BuiltInType.getSchemaTypeByLocalName(lname);
if (t == null) {
compileError("Unknown built-in type " + typeAtt, "XTSE1520");
return null;
}
return t;
}
// not a built-in type: look in the imported schemas
if (!getPrincipalStylesheetModule().isImportedSchema(uri)) {
compileError("There is no imported schema for the namespace of type " + typeAtt, "XTSE1520");
return null;
}
StructuredQName qName = new StructuredQName("", uri, lname);
SchemaType stype = getConfiguration().getSchemaType(qName);
if (stype == null) {
compileError("There is no type named " + typeAtt + " in an imported schema", "XTSE1520");
}
return stype;
} catch (QNameException err) {
compileError("Invalid type name. " + err.getMessage(), "XTSE1520");
}
return null;
}
/**
* Get the type annotation to use for a given schema type
*
* @param schemaType the schema type
* @return the corresponding numeric type annotation
*/
public SimpleType getTypeAnnotation(SchemaType schemaType) {
return (SimpleType) schemaType;
}
/**
* Adapt an expression that returns an array to one that returns a representation of
* the array as a sequence of single-entry maps, as required for the proposed XSLT 4.0
* xsl:for-each/iterate instructions with an "array" attribute
* @param arrayExpr the expression in the array attribute
* @return a call on a function that converts the array to a sequence of single-entry maps
*/
protected Expression arrayToSequence(Expression arrayExpr) {
try {
return ArrayFunctionSet.getInstance()
.makeFunction("members", 1)
.makeFunctionCall(arrayExpr);
} catch (XPathException e) {
throw new UncheckedXPathException(e);
}
}
/**
* Adapt an expression that returns a map to one that returns a representation of
* the map as a sequence of two-entry maps containing the "key" and "value" fields,
* as required for the proposed XSLT 4.0 xsl:for-each/iterate instructions with a "map" attribute
*
* @param mapExpr the expression in the map attribute
* @return a call on a function that converts the map to a sequence of key-value maps
*/
protected Expression mapToSequence(Expression mapExpr) {
try {
return VendorFunctionSetHE.getInstance()
.makeFunction("map-as-sequence-of-maps", 1)
.makeFunctionCall(mapExpr);
} catch (XPathException e) {
throw new UncheckedXPathException(e);
}
}
/**
* Check that the stylesheet element is valid. This is called once for each element, after
* the entire tree has been built. As well as validation, it can perform first-time
* initialisation. The default implementation does nothing; it is normally overriden
* in subclasses.
*
* @param decl the declaration to be validated
* @throws XPathException if any error is found during validation
*/
public void validate(ComponentDeclaration decl) throws XPathException {
}
/**
* Hook to allow additional validation of a parent element immediately after its
* children have been validated.
*
* @throws XPathException if any error is found during post-traversal validation
*/
public void postValidate() throws XPathException {
}
/**
* Method supplied by declaration elements to add themselves to a stylesheet-level index
*
* @param decl the Declaration being indexed. (This corresponds to the StyleElement object
* except in cases where one module is imported several times with different precedence.)
* @param top represents the outermost XSLStylesheet or XSLPackage element
* @throws XPathException if any error is encountered
*/
public void index(ComponentDeclaration decl, PrincipalStylesheetModule top) throws XPathException {
}
/**
* Type-check an expression. This is called to check each expression while the containing
* instruction is being validated. It is not just a static type-check, it also adds code
* to perform any necessary run-time type checking and/or conversion.
*
* @param name the name of the attribute containing the expression to be checked (used for diagnostics)
* @param exp the expression to be checked
* @return the (possibly rewritten) expression after type checking
* @throws XPathException if type-checking fails statically, that is, if it can be determined that the
* supplied value for the expression cannot possibly be of the required type
*/
// Note: the typeCheck() call is done at the level of individual path expression; the optimize() call is done
// for a template or function as a whole. We can't do it all at the function/template level because
// the static context (e.g. namespaces) changes from one XPath expression to another.
public Expression typeCheck(String name, Expression exp) throws XPathException {
if (exp == null) {
return null;
}
Configuration config = getConfiguration();
if (config.getBooleanProperty(Feature.STRICT_STREAMABILITY)) {
return exp;
}
try {
exp = exp.typeCheck(makeExpressionVisitor(), config.makeContextItemStaticInfo(Type.ITEM_TYPE, true));
exp = ExpressionTool.resolveCallsToCurrentFunction(exp);
// if (explaining) {
// System.err.println("Attribute '" + name + "' of element '" + getDisplayName() + "' at line " + getLineNumber() + ':');
// System.err.println("Static type: " +
// SequenceType.makeSequenceType(exp.getItemType(), exp.getCardinality()));
// System.err.println("Optimized expression tree:");
// exp.display(10, getNamePool(), System.err);
// }
// CodeInjector injector = getCompilation().getCompilerInfo().getCodeInjector();
// if (injector != null) {
// return injector.inject(exp, getStaticContext(), LocationKind.XPATH_IN_XSLT, new StructuredQName("", "", name));
// }
return exp;
} catch (XPathException err) {
// we can't report a dynamic error such as divide by zero unless the expression
// is actually executed.
//err.printStackTrace();
if (err.isReportableStatically()) {
err.setLocation(new AttributeLocation(this, StructuredQName.fromClarkName(name)));
compileError(err);
return exp;
} else {
ErrorExpression erexp = new ErrorExpression(new XmlProcessingException(err));
ExpressionTool.copyLocationInfo(exp, erexp);
return erexp;
}
}
}
/**
* Allocate slots in the local stack frame to range variables used in an XPath expression
*
* @param exp the XPath expression for which slots are to be allocated
*/
void allocateLocalSlots(Expression exp) {
SlotManager slotManager = getContainingSlotManager();
if (slotManager == null) {
throw new AssertionError("Slot manager has not been allocated");
} else {
int firstSlot = slotManager.getNumberOfVariables();
int highWater = ExpressionTool.allocateSlots(exp, firstSlot, slotManager);
if (highWater > firstSlot) {
slotManager.setNumberOfVariables(highWater);
// This algorithm is not very efficient because it never reuses
// a slot when a variable goes out of scope. But at least it is safe.
// Note that range variables within XPath expressions need to maintain
// a slot until the instruction they are part of finishes, e.g. in
// xsl:for-each.
}
}
}
/**
* Type-check a pattern. This is called to check each pattern while the containing
* instruction is being validated. It is not just a static type-check, it also adds code
* to perform any necessary run-time type checking and/or conversion.
*
* @param name the name of the attribute holding the pattern, for example "match": used in
* diagnostics
* @param pattern the compiled pattern
* @return the original pattern, or a substitute pattern if it has been rewritten. Returns null
* if and only if the supplied pattern is null.
* @throws net.sf.saxon.trans.XPathException if the pattern fails optimistic static type-checking
*/
public Pattern typeCheck(String name, Pattern pattern) throws XPathException {
if (pattern == null) {
return null;
}
try {
ItemType cit = Type.ITEM_TYPE;
pattern = pattern.typeCheck(makeExpressionVisitor(), getConfiguration().makeContextItemStaticInfo(cit, true));
boolean usesCurrent = false;
for (Operand o : pattern.operands()) {
Expression filter = o.getChildExpression();
if (ExpressionTool.callsFunction(filter, Current.FN_CURRENT, false)) {
usesCurrent = true;
break;
}
}
if (usesCurrent) {
PatternThatSetsCurrent p2 = new PatternThatSetsCurrent(pattern);
pattern.bindCurrent(p2.getCurrentBinding());
pattern = p2;
}
return pattern;
} catch (XPathException err) {
// we can't report a dynamic error such as divide by zero unless the pattern
// is actually executed. We don't have an error pattern available, so we
// construct one
if (err.isReportableStatically()) {
XPathException e2 = new XPathException("Error in " + name + " pattern", err);
e2.setLocator(this);
e2.setErrorCodeQName(err.getErrorCodeQName());
throw e2;
} else {
Pattern p = new BasePatternWithPredicate(
new NodeTestPattern(ErrorType.getInstance()),
new ErrorExpression(new XmlProcessingException(err)));
p.setLocation(allocateLocation());
return p;
}
}
}
/**
* Fix up references from XPath expressions. Overridden for function declarations
* and variable declarations
*
* @throws net.sf.saxon.trans.XPathException if any references cannot be fixed up.
*/
public void fixupReferences() throws XPathException {
for (NodeInfo child : children(StyleElement.class::isInstance)) {
((StyleElement) child).fixupReferences();
}
}
/**
* Get the SlotManager for the containing Procedure definition
*
* @return the SlotManager associated with the containing Function, Template, etc,
* or null if there is no such containing Function, Template etc.
*/
public SlotManager getContainingSlotManager() {
NodeImpl node = this;
while (true) {
NodeImpl next = node.getParent();
assert next != null;
if (next instanceof XSLModuleRoot || next.getFingerprint() == StandardNames.XSL_OVERRIDE) {
if (node instanceof StylesheetComponent) {
return ((StylesheetComponent) node).getSlotManager();
} else {
return null;
}
}
node = next;
}
}
/**
* Recursive walk through the stylesheet to validate all nodes
*
* @param decl the declaration to be validated
* @param excludeStylesheet true if the XSLStylesheet element is to be excluded
* @throws XPathException if validation fails
*/
public void validateSubtree(ComponentDeclaration decl, boolean excludeStylesheet) throws XPathException {
if (isActionCompleted(StyleElement.ACTION_VALIDATE)) {
return;
}
setActionCompleted(StyleElement.ACTION_VALIDATE);
if (validationError != null) {
if (reportingCircumstances == OnFailure.REPORT_ALWAYS) {
compileError(validationError);
} else if (reportingCircumstances == OnFailure.REPORT_UNLESS_FORWARDS_COMPATIBLE
&& !forwardsCompatibleModeIsEnabled()) {
compileError(validationError);
} else if (reportingCircumstances == OnFailure.REPORT_STATICALLY_UNLESS_FALLBACK_AVAILABLE) {
boolean hasFallback = false;
for (NodeInfo child : children(XSLFallback.class::isInstance)) {
hasFallback = true;
((XSLFallback) child).validateSubtree(decl, false);
}
if (!hasFallback) {
compileError(validationError);
}
} else if (reportingCircumstances == OnFailure.REPORT_DYNAMICALLY_UNLESS_FALLBACK_AVAILABLE) {
for (NodeInfo child : children(XSLFallback.class::isInstance)) {
((XSLFallback) child).validateSubtree(decl, false);
}
}
} else {
try {
validate(decl);
} catch (XPathException err) {
compileError(err);
}
validateChildren(decl, excludeStylesheet);
if (getCompilation().getErrorCount() == 0) {
postValidate();
}
}
}
/**
* Validate the children of this node, recursively. Overridden for top-level
* data elements.
*
* @param decl the declaration whose children are to be validated
* @param excludeStylesheet true if the xsl:stylesheet element is to be excluded
* @throws XPathException if validation fails
*/
protected void validateChildren(ComponentDeclaration decl, boolean excludeStylesheet) throws XPathException {
boolean containsInstructions = mayContainSequenceConstructor();
StyleElement lastChild = null;
boolean endsWithTextTemplate = false;
for (NodeInfo child : children()) {
if (child instanceof StyleElement) {
if (!(excludeStylesheet && child instanceof XSLStylesheet)) {
endsWithTextTemplate = false;
if (containsInstructions && !((StyleElement) child).isInstruction()
&& !isPermittedChild((StyleElement) child)) {
((StyleElement) child).compileError("An " + getDisplayName() + " element must not contain an " +
child.getDisplayName() + " element", "XTSE0010");
}
((StyleElement) child).validateSubtree(decl, excludeStylesheet);
lastChild = (StyleElement) child;
}
} else {
endsWithTextTemplate = examineTextNode(child);
}
}
if (lastChild instanceof XSLLocalVariable &&
!(this instanceof XSLStylesheet) && !endsWithTextTemplate) {
lastChild.compileWarning("A variable with no following sibling instructions has no effect",
SaxonErrorCode.SXWN9001);
}
}
/**
* Examine a text node in the stylesheet to see if it is a text value template
*
* @param node the text node
* @throws XPathException if the node is is a text value template with variable content
*/
private boolean examineTextNode(NodeInfo node) throws XPathException {
if (node instanceof TextValueTemplateNode) {
((TextValueTemplateNode) node).validate();
return !(((TextValueTemplateNode) node).getContentExpression() instanceof Literal);
} else {
return false;
}
}
/**
* Check whether a given child is permitted for this element. This method is used when a non-instruction
* child element such as xsl:sort is encountered in a context where instructions would normally be expected.
*
* @param child the child that may or may not be permitted
* @return true if the child is permitted.
*/
protected boolean isPermittedChild(StyleElement child) {
return false;
}
/**
* Get the principal stylesheet module of the package in which
* this XSLT element appears
*
* @return the containing package
*/
public PrincipalStylesheetModule getPrincipalStylesheetModule() {
return getCompilation().getPrincipalStylesheetModule();
}
/**
* Get the containing package (the principal stylesheet module of the package in which
* this XSLT element appears)
*
* @return the containing package. May be null if the method is called during initialization.
*/
public StylesheetPackage getContainingPackage() {
PrincipalStylesheetModule psm = getPrincipalStylesheetModule();
return psm==null ? null : psm.getStylesheetPackage();
}
/**
* Check that among the children of this element, any xsl:sort elements precede any other elements
*
* @param sortRequired true if there must be at least one xsl:sort element
*/
void checkSortComesFirst(boolean sortRequired) {
boolean sortFound = false;
boolean nonSortFound = false;
for (NodeInfo child : children()) {
if (child instanceof XSLSort) {
if (nonSortFound) {
((XSLSort) child).compileError("Within " + getDisplayName() +
", xsl:sort elements must come before other instructions", "XTSE0010");
}
sortFound = true;
} else if (child.getNodeKind() == Type.TEXT) {
// with xml:space=preserve, white space nodes may still be there
if (!Whitespace.isAllWhite(child.getUnicodeStringValue())) {
nonSortFound = true;
}
} else {
nonSortFound = true;
}
}
if (sortRequired && !sortFound) {
compileError(getDisplayName() + " must have at least one xsl:sort child", "XTSE0010");
}
}
/**
* Convenience method to check that the stylesheet element is at the top level (that is,
* as a child of xsl:stylesheet or xsl:transform)
*
* @param errorCode the error to throw if it is not at the top level; defaults to XTSE0010
* if the value is null
* @param allowOverride true if the element is allowed to appear as a child of xsl:override
*/
public void checkTopLevel(/*@NotNull*/ String errorCode, boolean allowOverride) {
NodeImpl parent = getParent();
assert parent != null;
if (parent.getFingerprint() == StandardNames.XSL_OVERRIDE) {
if (!allowOverride) {
compileError("Element " + getDisplayName() + " is not allowed as a child of xsl:override");
}
} else if (!isTopLevel()) {
compileError("Element " + getDisplayName() + " must be top-level (a child of xsl:stylesheet, xsl:transform, or xsl:package)", errorCode);
}
}
/**
* Convenience method to check that the stylesheet element is empty
*/
public void checkEmpty() {
if (hasChildNodes()) {
compileError("Element must be empty", "XTSE0260");
}
}
/**
* Convenience method to report the absence of a mandatory attribute
*
* @param attribute the name of the attribute whose absence is to be reported
*/
public void reportAbsence(String attribute) {
compileError("Element must have an " + Err.wrap(attribute, Err.ATTRIBUTE) + " attribute", "XTSE0010");
}
/**
* Compile the instruction on the stylesheet tree into an executable instruction
* for use at run-time.
*
* @param compilation the compilation episode
* @param decl the containing top-level declaration, for example xsl:function or xsl:template
* @return either a ComputedExpression, or null. The value null is returned when compiling an instruction
* that returns a no-op, or when compiling a top-level object such as an xsl:template that compiles
* into something other than an instruction.
* @throws net.sf.saxon.trans.XPathException if validation fails
*/
public Expression compile(Compilation compilation, ComponentDeclaration decl) throws XPathException {
// no action: default for non-instruction elements
return null;
}
protected boolean isWithinDeclaredStreamableConstruct() {
if (getURI().equals(NamespaceConstant.XSLT)) {
String streamableAtt = getAttributeValue("streamable");
if (streamableAtt != null) {
return processStreamableAtt(streamableAtt);
}
}
NodeInfo parent = getParent();
return parent instanceof StyleElement && ((StyleElement) parent).isWithinDeclaredStreamableConstruct();
}
protected String generateId() {
StringBuilder buff = new StringBuilder(16);
generateId(buff);
return buff.toString();
}
/**
* Compile a declaration in the stylesheet tree
* for use at run-time.
*
* @param compilation the compilation episode
* @param decl the containing top-level declaration, for example xsl:function or xsl:template
* @throws net.sf.saxon.trans.XPathException if compilation fails
*/
public void compileDeclaration(Compilation compilation, ComponentDeclaration decl) throws XPathException {
// no action: default for elements that are not declarations
}
/**
* Compile the children of this instruction on the stylesheet tree, adding the
* subordinate instructions to the parent instruction on the execution tree.
*
* @param compilation the Executable
* @param decl the Declaration of the containing top-level stylesheet element
* @param includeParams true if xsl:param elements are to be treated as child instructions (true
* for templates but not for functions)
* @return the compiled sequence constructor
* @throws net.sf.saxon.trans.XPathException if compilation fails
*/
public Expression compileSequenceConstructor(Compilation compilation, ComponentDeclaration decl,
boolean includeParams)
throws XPathException {
// If there are any xsl:on-empty or xsl:on-non-empty children, then reorder the children so
// that local variable declarations come first. This is necessary to ensure that the instructions
// remain part of a single "block", since the containing block affects the semantics of
// on-empty and on-non-empty. Moving variables to come first would probably be a safe strategy in all
// cases, but there might be a performance disadvantage in some cases, and it's unnecessarily disruptive,
// especially if there are calls on user extension functions having side-effects.
// Note: we have already bound variable references to their declarations at this stage, so the reordering
// does not change the scope of variables.
// We also move any on-empty instructions to the end of the list, since this makes streaming easier.
boolean containsEmptyTest = false;
for (NodeInfo child : children()) {
int fp = child.getFingerprint();
if (fp == StandardNames.XSL_ON_EMPTY || fp == StandardNames.XSL_ON_NON_EMPTY) {
containsEmptyTest = true;
}
}
if (containsEmptyTest) {
List vars = new ArrayList<>();
List onEmpties = new ArrayList<>();
List others = new ArrayList<>();
for (NodeInfo kid : children()) {
int fp = kid.getFingerprint();
if (fp == StandardNames.XSL_VARIABLE || fp == StandardNames.XSL_PARAM) {
vars.add(kid);
} else if (fp == StandardNames.XSL_ON_EMPTY) {
onEmpties.add(kid);
} else {
others.add(kid);
}
}
vars.addAll(others);
vars.addAll(onEmpties);
return compileSequenceConstructor(compilation, decl, new NodeListIterator(vars), includeParams);
} else {
return compileSequenceConstructor(compilation, decl, iterateAxis(AxisInfo.CHILD), includeParams);
}
}
/**
* Compile the children of this instruction on the stylesheet tree, adding the
* subordinate instructions to the parent instruction on the execution tree.
*
* @param compilation the Executable
* @param decl the Declaration of the containing top-level stylesheet element
* @param iter Iterator over the children. This is used in the case where there are children
* that are not part of the sequence constructor, for example the xsl:sort children of xsl:for-each;
* the iterator can be positioned past such elements.
* @param includeParams true if xsl:param elements are to be treated as child instructions (true
* for templates but not for functions)
* @return the compiled sequence constructor
* @throws net.sf.saxon.trans.XPathException if compilation fails
*/
public Expression compileSequenceConstructor(Compilation compilation, ComponentDeclaration decl,
SequenceIterator iter, boolean includeParams)
throws XPathException {
Location locationId = allocateLocation();
List contents = new ArrayList<>(10);
boolean containsSpecials = false;
NodeInfo node;
while ((node = (NodeInfo) iter.next()) != null) {
if (node.getNodeKind() == Type.TEXT) {
if (isExpandingText()) {
compileContentValueTemplate((TextImpl) node, contents);
} else {
// handle literal text nodes by generating an xsl:value-of instruction, unless expand-text is enabled
AxisIterator lookahead = node.iterateAxis(AxisInfo.FOLLOWING_SIBLING);
NodeInfo sibling = lookahead.next();
if (!(sibling instanceof XSLLocalParam || sibling instanceof XSLSort
|| sibling instanceof XSLContextItem || sibling instanceof XSLOnCompletion)) {
// The test for XSLParam and XSLSort is to eliminate whitespace nodes that have been retained
// because of xml:space="preserve"
Expression text = new ValueOf(new StringLiteral(node.getUnicodeStringValue()), false, false);
text.setLocation(allocateLocation());
// CodeInjector injector = getCompilation().getCompilerInfo().getCodeInjector();
// if (injector != null) {
// Expression tracer = injector.inject(text);
// tracer.setLocation(text.getLocation());
// text = tracer;
// }
contents.add(text);
}
}
} else if (node instanceof XSLLocalVariable) {
XSLLocalVariable var = (XSLLocalVariable) node;
SourceBinding sourceBinding = var.getSourceBinding();
var.compileLocalVariable(compilation, decl);
Expression tail = compileSequenceConstructor(compilation, decl, iter, includeParams);
if (tail == null || Literal.isEmptySequence(tail)) {
// this doesn't happen, because if there are no instructions following
// a variable, we'll have taken the var==null path above
//return result;
} else {
LetExpression let = new LetExpression();
let.setInstruction(true);
let.setRequiredType(var.getRequiredType());
let.setVariableQName(sourceBinding.getVariableQName());
let.setSequence(sourceBinding.getSelectExpression());
let.setAction(tail);
sourceBinding.fixupBinding(let);
locationId = ((StyleElement) node).allocateLocation();
let.setLocation(locationId);
// if (getCompilation().getCompilerInfo().isCompileWithTracing()) {
// TraceExpression t = new TraceExpression(let);
// t.setConstructType(LocationKind.LET_EXPRESSION);
// t.setObjectName(var.getSourceBinding().getVariableQName());
// t.setNamespaceResolver(getNamespaceResolver());
// contents.add(t);
// } else {
contents.add(let);
// }
if (var.changesRetainedStaticContext()) {
let.setRetainedStaticContext(makeRetainedStaticContext());
}
//result.setLocationId(locationId);
}
} else if (node instanceof StyleElement) {
StyleElement snode = (StyleElement) node;
int fp = snode.getFingerprint();
if (fp == StandardNames.XSL_ON_EMPTY || fp == StandardNames.XSL_ON_NON_EMPTY) {
containsSpecials = true;
}
Expression child;
if (snode.validationError != null && !(snode instanceof AbsentExtensionElement)) {
if (snode.reportingCircumstances == OnFailure.REPORT_IF_INSTANTIATED) {
child = new ErrorExpression(snode.validationError);
} else {
child = fallbackProcessing(compilation, decl, snode);
}
} else {
child = snode.compile(compilation, decl);
if (child != null) {
if (snode.changesRetainedStaticContext()) {
child.setRetainedStaticContext(snode.makeRetainedStaticContext());
}
setInstructionLocation(snode, child);
}
}
if (child != null) {
contents.add(child);
}
}
}
if (containsSpecials) {
return new ConditionalBlock(contents);
}
Expression block = Block.makeBlock(contents);
if (block.getLocation() == null) {
block.setLocation(locationId);
}
if (block.getLocalRetainedStaticContext() == null) {
block.setRetainedStaticContext(makeRetainedStaticContext());
}
return block;
}
/**
* Compile a content value text node.
*
* @param node the text node potentially containing the template
* @param contents a list to which expressions representing the fixed and variable parts of the content template
* will be appended
*/
void compileContentValueTemplate(TextImpl node, List contents) {
if (node instanceof TextValueTemplateNode) {
Expression exp = ((TextValueTemplateNode) node).getContentExpression();
if (getConfiguration().getBooleanProperty(Feature.STRICT_STREAMABILITY) && !(exp instanceof Literal)) {
exp = new SequenceInstr(exp);
}
contents.add(exp);
} else {
contents.add(new StringLiteral(node.getUnicodeStringValue()));
}
}
/**
* Set location information on a compiled instruction
*
* @param source the parent element
* @param child the compiled expression tree for the instruction to be traced
*/
protected static void setInstructionLocation(StyleElement source, Expression child) {
if (child.getLocation() == null || child.getLocation() == Loc.NONE) {
child.setLocation(source.saveLocation());
}
}
/**
* Perform fallback processing. Generate fallback code for an extension
* instruction that is not recognized by the implementation.
*
* @param exec the Executable
* @param decl the Declaration of the top-level element containing the extension instruction
* @param instruction The unknown extension instruction
* @return the expression tree representing the fallback code
* @throws net.sf.saxon.trans.XPathException if any error occurs
*/
Expression fallbackProcessing(Compilation exec, ComponentDeclaration decl, StyleElement instruction)
throws XPathException {
// process any xsl:fallback children; if there are none,
// generate code to report the original failure reason
Expression fallback = null;
for (NodeInfo child : children(XSLFallback.class::isInstance)) {
Expression b = ((XSLFallback) child).compileSequenceConstructor(exec, decl, true);
if (b == null) {
b = Literal.makeEmptySequence();
}
if (fallback == null) {
fallback = b;
} else {
fallback = Block.makeBlock(fallback, b);
fallback.setLocation(allocateLocation());
}
}
if (fallback != null) {
return fallback;
} else {
return new ErrorExpression(instruction.validationError);
}
}
/**
* Allocate a location
*
* @return an location which can be used to report the location of the instruction
*/
protected Location allocateLocation() {
if (savedLocation == null) {
savedLocation = new Loc(this);
}
return savedLocation;
}
/**
* Construct sort keys for a SortedIterator
*
* @param compilation the compilation episode
* @param decl the declaration containing the sort keys @throws XPathException if any error is detected
* @return an array of SortKeyDefinition objects if there are any sort keys;
* or null if there are none.
* @throws XPathException if an error is found
*/
SortKeyDefinitionList makeSortKeys(Compilation compilation, ComponentDeclaration decl) throws XPathException {
// handle sort keys if any
int numberOfSortKeys = 0;
for (NodeInfo child : children(XSLSortOrMergeKey.class::isInstance)) {
((XSLSortOrMergeKey) child).compile(compilation, decl);
if (child instanceof XSLSort) {
if (numberOfSortKeys != 0 && ((XSLSort) child).getStable() != null) {
compileError("stable attribute may appear only on the first xsl:sort element", "XTSE1017");
}
}
numberOfSortKeys++;
}
if (numberOfSortKeys > 0) {
SortKeyDefinition[] keys = new SortKeyDefinition[numberOfSortKeys];
int k = 0;
for (NodeInfo child : children(XSLSortOrMergeKey.class::isInstance)) {
keys[k++] = (SortKeyDefinition) ((XSLSortOrMergeKey) child).getSortKeyDefinition().simplify();
}
return new SortKeyDefinitionList(keys);
} else {
return null;
}
}
/**
* Get the list of attribute-set names associated with this element.
* This is used for xsl:element, xsl:copy, xsl:attribute-set, and on literal
* result elements
*
* @param use the original value of the [xsl:]use-attribute-sets attribute
* @return an array of names of the attribute sets
*/
StructuredQName[] getUsedAttributeSets(String use) {
List nameList = new ArrayList<>(4);
StringTokenizer st = new StringTokenizer(use, " \t\n\r", false);
while (st.hasMoreTokens()) {
String asetname = st.nextToken();
StructuredQName name = makeQName(asetname, "XTSE0710", "use-attribute-sets");
nameList.add(name);
}
return nameList.toArray(new StructuredQName[0]);
}
/**
* Process the value of the visibility attribute (XSLT 3.0). Invokes
* {@link #invalidAttribute(String, String)} if the value is invalid.
*
* @param s the value of the attribute after whitespace collapsing
* @param flags contains "h" if the value "hidden" is allowed, "a" if the value "absent" is allowed
* @return the corresponding visibility
*/
Visibility interpretVisibilityValue(String s, String flags) {
for (Visibility v : Visibility.values()) {
if (v.toString().toLowerCase().equals(s) &&
(flags.contains("h") || !s.equals("hidden")) &&
(flags.contains("a") || !s.equals("absent"))) {
return v;
}
}
invalidAttribute("visibility", "public|final|private|abstract" +
(flags.contains("h") ? "|hidden" : "") +
(flags.contains("a") ? "|absent" : "")
);
return Visibility.UNDEFINED;
}
/**
* Get the list of xsl:with-param elements for a calling element (apply-templates,
* call-template, apply-imports, next-match). This method can be used to get either
* the tunnel parameters, or the non-tunnel parameters.
* @param parent the compiled form of the parent instruction
* @param compilation the compilation episode
* @param decl the containing stylesheet declaration
* @param tunnel true if the tunnel="yes" parameters are wanted, false to get
* @return an array of WithParam objects for either the ordinary parameters
* or the tunnel parameters, as an array containing the results of
* compiling the xsl:with-param children of this instruction (if any)
* @throws XPathException if any error is detected
*/
public WithParam[] getWithParamInstructions(Expression parent, Compilation compilation,
ComponentDeclaration decl, boolean tunnel)
throws XPathException {
int count = 0;
for (NodeInfo child : children(XSLWithParam.class::isInstance)) {
XSLWithParam wp = (XSLWithParam) child;
if (wp.getSourceBinding().hasProperty(SourceBinding.BindingProperty.TUNNEL) == tunnel) {
count++;
}
}
if (count == 0) {
return WithParam.EMPTY_ARRAY;
}
WithParam[] array = new WithParam[count];
count = 0;
for (NodeInfo child : children(XSLWithParam.class::isInstance)) {
XSLWithParam wp = (XSLWithParam) child;
if (wp.getSourceBinding().hasProperty(SourceBinding.BindingProperty.TUNNEL) == tunnel) {
WithParam p = wp.compileWithParam(parent, compilation, decl);
if (wp.getParent() instanceof XSLNextIteration && wp.hasChildNodes()) {
// Type-check against the declared type of the xsl:param, unless this was done earlier
SequenceType required = ((XSLNextIteration) wp.getParent()).getDeclaredParamType(
wp.getSourceBinding().getVariableQName());
wp.checkAgainstRequiredType(required);
p.getSelectOperand().setChildExpression(wp.sourceBinding.getSelectExpression());
}
array[count++] = p;
}
}
return array;
}
/**
* Report an error with diagnostic information
*
* @param error contains information about the error
*/
public void compileError(XmlProcessingError error) {
XmlProcessingIncident.maybeSetHostLanguage(error, HostLanguage.XSLT);
// Set the location of the error if there is no current location information,
// or if the current location information is local to an XPath expression, unless we are
// positioned on an xsl:function or xsl:template, in which case this would lose too much information
if (error.getLocation() == null ||
((error.getLocation() instanceof Loc ||
error.getLocation() instanceof Expression) && !(this instanceof StylesheetComponent))) {
XmlProcessingIncident.maybeSetLocation(error, this);
}
getCompilation().reportError(error);
}
public void compileError(XPathException err) {
if (err.getLocator() == null) {
err.setLocation(this);
}
XmlProcessingIncident se = new XmlProcessingIncident(err.getMessage(), err.getErrorCodeLocalPart(), err.getLocator());
se.setHostLanguage(HostLanguage.XSLT);
compileError(se);
}
/**
* Report a static error in the stylesheet
*
* @param message the error message
*/
public void compileError(String message) {
compileError(message, "XTSE0010");
}
/**
* Compile time error, specifying an error code
*
* @param message the error message
* @param errorCode the error code. May be null if not known or not defined
*/
public void compileError(String message, StructuredQName errorCode) {
XmlProcessingIncident error = new XmlProcessingIncident(message, errorCode.getEQName(), this);
error.setHostLanguage(HostLanguage.XSLT);
compileError(error);
}
/**
* Compile time error, specifying an error code
*
* @param message the error message
* @param errorCode the error code. May be null if not known or not defined
*/
public void compileError(String message, String errorCode) {
compileError(new XPathException(message, errorCode, this));
}
public void compileError(String message, String errorCode, Location loc) {
compileError(new XPathException(message, errorCode, loc));
}
/**
* Compile time error, specifying an error code and the name of the attribute that
* is in error.
*
* @param message the error message
* @param errorCode the error code. May be null if not known or not defined
* @param attributeName the name of the attribute. For attributes in no namespace
* this is the local part of the name; for namespaced attributes
* a name in Clark format may be supplied.
*/
public void compileErrorInAttribute(String message, String errorCode, String attributeName) {
StructuredQName att = StructuredQName.fromClarkName(attributeName);
Location location = new AttributeLocation(this, att);
compileError(new XPathException(message, errorCode, location));
}
protected void invalidAttribute(String attributeName, String allowedValues) {
compileErrorInAttribute("Attribute " + getDisplayName() + "/@" + attributeName + " must be " + allowedValues,
"XTSE0020", attributeName);
}
/**
* Report an error unless XSLT 4.0 syntax is enabled. This currently requires the XSLT version to be set to "4.0"
* in the XSLT compiler, AND the effective version in the stylesheet to be 4.0 or greater.
* @param attributeName the name of the attribute if relevant, or null if no specific attribute is concerned
*/
protected void requireXslt40(String attributeName) {
if (compilation.getCompilerInfo().getXsltVersion() != 40) {
if (attributeName == null) {
compileError("Element " + getDisplayName() + " is allowed only if XSLT 4.0 is enabled", "XTSE0010");
} else {
compileErrorInAttribute("Attribute " + getDisplayName() + "/@" + attributeName + " is allowed only if XSLT 4.0 is enabled",
"XTSE0020", attributeName);
}
}
}
void undeclaredNamespaceError(String prefix, String errorCode, String attributeName) {
if (errorCode == null) {
errorCode = "XTSE0280";
}
compileErrorInAttribute("Undeclared namespace prefix " + Err.wrap(prefix), errorCode, attributeName);
}
public void compileWarning(String message, StructuredQName errorCode) {
getCompilation().reportWarning(message, errorCode.getEQName(), this);
}
public void compileWarning(String message, String errorCode) {
getCompilation().reportWarning(message, errorCode, this);
}
public void compileWarning(String message, String errorCode, Location location) {
getCompilation().reportWarning(message, errorCode, location);
}
/**
* Report a warning to the error listener
*
* @param error an exception containing the warning text
*/
protected void issueWarning(XPathException error) {
if (error.getLocator() == null) {
error.setLocator(this);
}
getCompilation().reportWarning(error);
}
/**
* Report a warning to the error listener
*
* @param message the warning message text
* @param locator the location of the problem in the source stylesheet
*/
protected void issueWarning(String message, SourceLocator locator) {
XPathException tce = new XPathException(message);
if (locator == null) {
tce.setLocator(this);
} else {
tce.setLocator(locator);
}
issueWarning(tce);
}
/**
* Test whether this is a top-level element
*
* @return true if the element is a child of the xsl:stylesheet or xsl:package element
*/
public boolean isTopLevel() {
return getParent() instanceof XSLModuleRoot;
}
/**
* Ask whether this is an instruction that is known to be constructing nodes which
* will become children of a parent document or element node, and will not have an
* independent existence of their own.
*
* @return true if it is known that this is an instruction that creates nodes that
* will immediately be attached to a parent element or document node
*/
boolean isConstructingComplexContent() {
if (!isInstruction()) {
return false;
}
NodeInfo parent = getParent();
while (true) {
if (!(parent instanceof StyleElement && ((StyleElement) parent).isInstruction())) {
return false;
}
if (parent instanceof XSLGeneralVariable) {
return ((XSLGeneralVariable) parent).getAttributeValue("as") == null;
}
if (parent instanceof XSLElement || parent instanceof LiteralResultElement || parent instanceof XSLDocument || parent instanceof XSLCopy) {
return true;
}
parent = parent.getParent();
}
}
/**
* Ask whether this element contains a binding for a variable with a given name; and if it does,
* return the source binding information
*
* @param name the variable name
* @return the binding information if this element binds a variable of this name; otherwise null
*/
public SourceBinding getBindingInformation(StructuredQName name) {
return null;
}
/**
* Bind a variable used in this element to the compiled form of the XSLVariable element in which it is
* declared
*
* @param qName The name of the variable
* @return the XSLVariableDeclaration (that is, an xsl:variable or xsl:param instruction) for the variable,
* or null if no declaration of the variable can be found
*/
public SourceBinding bindVariable(StructuredQName qName) {
SourceBinding decl = bindLocalVariable(qName);
if (decl != null) {
return decl;
}
// Now check for a global variable
// we rely on the search following the order of decreasing import precedence.
SourceBinding binding = getPrincipalStylesheetModule().getGlobalVariableBinding(qName);
if (binding == null || Navigator.isAncestorOrSelf(binding.getSourceElement(), this)) {
// test case variable-0118
return null;
} else {
return binding;
}
}
/**
* Bind a variable reference used in this element to the compiled form of the XSLVariable element in which it is
* declared, considering only local variables and params
*
* @param qName The name of the variable
* @return the XSLVariableDeclaration (that is, an xsl:variable or xsl:param instruction) for the variable,
* or null if no local declaration of the variable can be found
*/
public SourceBinding bindLocalVariable(StructuredQName qName) {
NodeInfo curr = this;
NodeInfo prev = this;
SourceBinding implicit = hasImplicitBinding(qName);
if (implicit != null) {
return implicit;
}
// first search for a local variable declaration
if (!isTopLevel()) {
AxisIterator preceding = curr.iterateAxis(AxisInfo.PRECEDING_SIBLING);
while (true) {
curr = preceding.next();
while (curr == null) {
curr = prev.getParent();
if (curr instanceof StyleElement) {
implicit = ((StyleElement) curr).hasImplicitBinding(qName);
if (implicit != null) {
return implicit;
}
}
while (curr instanceof StyleElement && !((StyleElement) curr).seesAvuncularVariables()) {
// a local variable is not visible within a sibling xsl:fallback or xsl:catch element
curr = curr.getParent();
}
prev = curr;
if (curr.getParent() instanceof XSLModuleRoot) {
break; // top level
}
preceding = curr.iterateAxis(AxisInfo.PRECEDING_SIBLING);
curr = preceding.next();
}
if (curr.getParent() instanceof XSLModuleRoot) {
break;
}
if (curr instanceof XSLGeneralVariable) {
SourceBinding sourceBinding = ((XSLGeneralVariable) curr).getBindingInformation(qName);
if (sourceBinding != null) {
return sourceBinding;
}
}
}
}
return null;
}
/**
* Ask whether variables declared in an "uncle" element are visible.
*
* @return true for all elements except xsl:fallback and xsl:catch
*/
protected boolean seesAvuncularVariables() {
return true;
}
/**
* Ask whether this particular element implicitly binds a given variable (used for xsl:accumulator-rule)
* @param name the name of the variable
* @return a {@code SourceBinding} object representing the binding of this variable if it
* exists; otherwise null;
*/
protected SourceBinding hasImplicitBinding(StructuredQName name) {
return null;
}
/**
* Get a name identifying the object of the expression, for example a function name, template name,
* variable name, key name, element name, etc. This is used only where the name is known statically.
* If there is no name, the value will be null.
*
* @return the name of the object declared in this element, if any
*/
public StructuredQName getObjectName() {
return objectName;
}
/**
* Set the object name, for example the name of a function, variable, or template declared on this element
*
* @param qName the object name as a QName
*/
public void setObjectName(StructuredQName qName) {
objectName = qName;
}
/**
* Get an iterator over all the properties available. The values returned by the iterator
* will be of type String, and each string can be supplied as input to the getProperty()
* method to retrieve the value of the property.
* @return an iterator over the available property names
*/
public Iterator getProperties() {
List list = new ArrayList<>(10);
for (AttributeInfo att : attributes()) {
list.add(att.getNodeName().getStructuredQName().getClarkName());
}
return list.iterator();
}
/**
* Ask if an action on this StyleElement has been completed
*
* @param action for example ACTION_VALIDATE
* @return true if the action has already been performed
*/
boolean isActionCompleted(int action) {
return (actionsCompleted & action) != 0;
}
/**
* Say that an action on this StyleElement has been completed
*
* @param action for example ACTION_VALIDATE
*/
void setActionCompleted(int action) {
actionsCompleted |= action;
}
}