net.sf.saxon.style.AttributeValueTemplate 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-2023 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.style;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.RetainedStaticContext;
import net.sf.saxon.expr.parser.Token;
import net.sf.saxon.expr.parser.XPathParser;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.trans.SaxonErrorCode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.IntegerValue;
import net.sf.saxon.value.StringValue;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents an attribute value template. The class allows an AVT to be parsed, and
* can construct an Expression that returns the effective value of the AVT.
* This is an abstract class that is never instantiated, it contains static methods only.
*/
public abstract class AttributeValueTemplate {
private AttributeValueTemplate() {
}
/**
* Static factory method to create an AVT from an XSLT string representation. The
* method is also used for text value templates.
* @param avt the attribute value template (or TVT) as written.
* @param env the static context
* @return an expression that computes the value of the attribute / content, as a string.
* In the case of a TVT this must be further processed to create a text node.
* @throws XPathException if a static error is found
*/
public static Expression make(String avt, StaticContext env) throws XPathException {
List components = new ArrayList<>(5);
int i0, i1, i8, i9;
int len = avt.length();
int last = 0;
while (last < len) {
i0 = avt.indexOf("{", last);
i1 = avt.indexOf("{{", last);
i8 = avt.indexOf("}", last);
i9 = avt.indexOf("}}", last);
if ((i0 < 0 || len < i0) && (i8 < 0 || len < i8)) { // found end of string
addStringComponent(components, avt, last, len);
break;
} else if (i8 >= 0 && (i0 < 0 || i8 < i0)) { // found a "}"
if (i8 != i9) { // a "}" that isn't a "}}"
XPathException err = new XPathException("Closing curly brace in attribute value template \"" + avt.substring(0, len) + "\" must be doubled");
err.setErrorCode("XTSE0370");
err.setIsStaticError(true);
throw err;
}
addStringComponent(components, avt, last, i8 + 1);
last = i8 + 2;
} else if (i1 >= 0 && i1 == i0) { // found a doubled "{{"
addStringComponent(components, avt, last, i1 + 1);
last = i1 + 2;
} else if (i0 >= 0) { // found a single "{"
if (i0 > last) {
addStringComponent(components, avt, last, i0);
}
Expression exp;
XPathParser parser = env.getConfiguration().newExpressionParser("XP", false);
//parser.setDefaultContainer(container);
//parser.setLanguage(XPathParser.ParsedLanguage.XPATH, 31);
parser.setAllowAbsentExpression(true);
exp = parser.parse(avt, i0 + 1, Token.RCURLY, env);
exp.setRetainedStaticContext(env.makeRetainedStaticContext());
exp = exp.simplify();
last = parser.getTokenizer().currentTokenStartOffset + 1;
if (env instanceof ExpressionContext && ((ExpressionContext)env).getStyleElement() instanceof XSLAnalyzeString
&& isIntegerOrIntegerPair(exp)) {
env.issueWarning("Found {" + showIntegers(exp) + "} in regex attribute: perhaps {{" +
showIntegers(exp) + "}} was intended? (The attribute is an AVT, so curly braces should be doubled)", SaxonErrorCode.SXWN9036, exp.getLocation());
}
if (env.isInBackwardsCompatibleMode()) {
components.add(makeFirstItem(exp, env));
} else {
components.add(XSLLeafNodeConstructor.makeSimpleContentConstructor(
exp,
new StringLiteral(StringValue.SINGLE_SPACE), env).simplify());
}
} else {
throw new IllegalStateException("Internal error parsing AVT");
}
}
Expression result;
// is it empty?
if (components.isEmpty()) {
result = new StringLiteral(StringValue.EMPTY_STRING);
}
// is it a single component?
else if (components.size() == 1) {
result = components.get(0).simplify();
}
// otherwise, return an expression that concatenates the components
else {
Expression[] args = new Expression[components.size()];
args = components.toArray(args);
Expression fn = SystemFunction.makeCall("concat", new RetainedStaticContext(env), args);
result = fn.simplify();
}
result.setLocation(env.getContainingLocation());
return result;
}
/**
* Used to detect warning condition when braces are undoubled in the regex attribute of xsl:analyze-string
*
* @param exp an expression
* @return true if the expression is an integer literal or a pair of two integer literals
*/
private static boolean isIntegerOrIntegerPair(Expression exp) {
if (exp instanceof Literal) {
GroundedValue val = ((Literal) exp).getGroundedValue();
if (val instanceof IntegerValue) {
return true;
}
if (val.getLength() == 2) {
return val.itemAt(0) instanceof IntegerValue && val.itemAt(1) instanceof IntegerValue;
}
}
return false;
}
/**
* Used to report warning condition when braces are undoubled in the regex attribute of xsl:analyze-string
*
* @param exp an expression
* @return string representation of an integer literal or a pair of two integer literals
*/
private static String showIntegers(Expression exp) {
if (exp instanceof Literal) {
GroundedValue val = ((Literal) exp).getGroundedValue();
if (val instanceof IntegerValue) {
return val.toString();
}
if (val.getLength() == 2) {
if (val.itemAt(0) instanceof IntegerValue && val.itemAt(1) instanceof IntegerValue) {
return val.itemAt(0).toString() + "," + val.itemAt(1).toString();
}
}
}
return "";
}
private static void addStringComponent(List components, String avt, int start, int end) {
if (start < end) {
components.add(new StringLiteral(avt.substring(start, end)));
}
}
/**
* Make an expression that extracts the first item of a sequence, after atomization
* @param exp an expression that evaluates to a sequence
* @param env the static context of the expression
* @return an expression that returns the first item in the sequence returned by {@code exp}
*/
/*@NotNull*/
public static Expression makeFirstItem(Expression exp, StaticContext env) {
if (Literal.isEmptySequence(exp)) {
return exp;
}
final TypeHierarchy th = env.getConfiguration().getTypeHierarchy();
if (!exp.getItemType().isPlainType()) {
exp = Atomizer.makeAtomizer(exp, null);
}
if (Cardinality.allowsMany(exp.getCardinality())) {
exp = FirstItemExpression.makeFirstItemExpression(exp);
}
if (!th.isSubType(exp.getItemType(), BuiltInAtomicType.STRING)) {
exp = new AtomicSequenceConverter(exp, BuiltInAtomicType.STRING);
((AtomicSequenceConverter) exp).allocateConverterStatically(env.getConfiguration(), false);
}
return exp;
}
}