You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@xalan.apache.org by mu...@apache.org on 2023/07/16 15:47:09 UTC
[xalan-java] branch xalan-j_xslt3.0 updated: committing implementation of xpath 3.1 'let' expression and related codebase changes, and few new working related test cases as well
This is an automated email from the ASF dual-hosted git repository.
mukulg pushed a commit to branch xalan-j_xslt3.0
in repository https://gitbox.apache.org/repos/asf/xalan-java.git
The following commit(s) were added to refs/heads/xalan-j_xslt3.0 by this push:
new 69d94dae committing implementation of xpath 3.1 'let' expression and related codebase changes, and few new working related test cases as well
new 598dfa3e Merge pull request #30 from mukulga/xalan-j_xslt3.0_mukul
69d94dae is described below
commit 69d94dae459dd9eeaeac340fd2adfa54fac8908d
Author: Mukul Gandhi <ga...@gmail.com>
AuthorDate: Sun Jul 16 21:12:52 2023 +0530
committing implementation of xpath 3.1 'let' expression and related codebase changes, and few new working related test cases as well
---
src/org/apache/xalan/templates/ElemVariable.java | 16 ++-
src/org/apache/xalan/templates/StylesheetRoot.java | 31 ++++-
src/org/apache/xpath/Expression.java | 8 +-
src/org/apache/xpath/XPathContext.java | 3 +-
src/org/apache/xpath/compiler/Compiler.java | 7 +
src/org/apache/xpath/compiler/Lexer.java | 87 +++++++++----
src/org/apache/xpath/compiler/OpCodes.java | 8 +-
src/org/apache/xpath/compiler/XPathParser.java | 97 +++++++++++++-
src/org/apache/xpath/composite/ForExpr.java | 2 +-
src/org/apache/xpath/composite/LetExpr.java | 145 +++++++++++++++++++++
.../apache/xpath/composite/LetExprVarBinding.java | 52 ++++++++
.../xpath/functions/DynamicFunctionCall.java | 42 +++++-
src/org/apache/xpath/operations/Variable.java | 2 +-
tests/let_expr/gold/test1.out | 1 +
tests/let_expr/gold/test2.out | 1 +
tests/let_expr/test1.xsl | 38 ++++++
tests/let_expr/test1_a.xml | 33 +++++
tests/let_expr/test2.xsl | 46 +++++++
tests/let_expr/test3.xsl | 42 ++++++
tests/org/apache/xalan/xpath3/LetExprTests.java | 80 ++++++++++++
tests/org/apache/xalan/xslt3/AllXsl3Tests.java | 3 +-
21 files changed, 693 insertions(+), 51 deletions(-)
diff --git a/src/org/apache/xalan/templates/ElemVariable.java b/src/org/apache/xalan/templates/ElemVariable.java
index 2505408c..5532a629 100644
--- a/src/org/apache/xalan/templates/ElemVariable.java
+++ b/src/org/apache/xalan/templates/ElemVariable.java
@@ -17,6 +17,8 @@
*/
package org.apache.xalan.templates;
+import java.util.Map;
+
import javax.xml.transform.TransformerException;
import org.apache.xalan.res.XSLTErrorResources;
@@ -28,6 +30,7 @@ import org.apache.xpath.XPathContext;
import org.apache.xpath.axes.SelfIteratorNoPredicate;
import org.apache.xpath.functions.FuncExtFunction;
import org.apache.xpath.functions.Function;
+import org.apache.xpath.objects.InlineFunction;
import org.apache.xpath.objects.ResultSequence;
import org.apache.xpath.objects.XNodeSetForDOM;
import org.apache.xpath.objects.XObject;
@@ -258,9 +261,16 @@ public class ElemVariable extends ElemTemplateElement
int sourceNode = transformer.getXPathContext().getCurrentNode();
XObject var = getValue(transformer, sourceNode);
-
- // transformer.getXPathContext().getVarStack().pushVariable(m_qname, var);
- transformer.getXPathContext().getVarStack().setLocalVariable(m_index, var);
+
+ if (var instanceof InlineFunction)
+ {
+ Map<QName, XObject> xpathVarMap = (transformer.getXPathContext()).getXPathVarMap();
+ xpathVarMap.put(m_qname, var);
+ }
+ else {
+ // transformer.getXPathContext().getVarStack().pushVariable(m_qname, var);
+ transformer.getXPathContext().getVarStack().setLocalVariable(m_index, var);
+ }
if (transformer.getDebug())
transformer.getTraceManager().fireTraceEndEvent(this);
diff --git a/src/org/apache/xalan/templates/StylesheetRoot.java b/src/org/apache/xalan/templates/StylesheetRoot.java
index df79f811..9ac49c54 100644
--- a/src/org/apache/xalan/templates/StylesheetRoot.java
+++ b/src/org/apache/xalan/templates/StylesheetRoot.java
@@ -24,6 +24,7 @@ import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
+import java.util.Map;
import java.util.Properties;
import java.util.Vector;
@@ -43,8 +44,10 @@ import org.apache.xml.dtm.DTM;
import org.apache.xml.dtm.ref.ExpandedNameTable;
import org.apache.xml.utils.IntStack;
import org.apache.xml.utils.QName;
+import org.apache.xpath.Expression;
import org.apache.xpath.XPath;
import org.apache.xpath.XPathContext;
+import org.apache.xpath.objects.InlineFunction;
/**
* This class represents the root object of the stylesheet tree.
@@ -74,7 +77,14 @@ public class StylesheetRoot extends StylesheetComposed
* State of the secure processing feature.
*/
private boolean m_isSecureProcessing = false;
-
+
+ /**
+ * We store stylesheet global variable declarations that refer to
+ * XPath 3.1 inline functions, within this java.util.Map object.
+ */
+ private Map<QName, InlineFunction> m_inlineFunctionVarMap =
+ new HashMap<QName, InlineFunction>();
+
/**
* Uses an XSL stylesheet document.
* @throws TransformerConfigurationException if the baseIdentifier can not be resolved to a URL.
@@ -832,6 +842,16 @@ public class StylesheetRoot extends StylesheetComposed
elemVar.setIsTopLevel(true); // Mark as a top-level variable or param
elemVar.setIndex(m_variables.size());
m_variables.addElement(elemVar);
+
+ XPath selectXPath = elemVar.getSelect();
+ if (selectXPath != null) {
+ Expression selectExpression = selectXPath.getExpression();
+ if (selectExpression instanceof InlineFunction) {
+ QName elemVarQname = elemVar.getName();
+ m_inlineFunctionVarMap.put(elemVarQname, (InlineFunction)
+ selectExpression);
+ }
+ }
}
}
@@ -1402,4 +1422,13 @@ public class StylesheetRoot extends StylesheetComposed
m_source_location = b;
}
+ public Map<QName, InlineFunction> getInlineFunctionVarMap() {
+ return m_inlineFunctionVarMap;
+ }
+
+ public void setInlineFunctionVarMap(Map<QName, InlineFunction>
+ inlineFunctionVarMap) {
+ this.m_inlineFunctionVarMap = inlineFunctionVarMap;
+ }
+
}
diff --git a/src/org/apache/xpath/Expression.java b/src/org/apache/xpath/Expression.java
index 5b580a52..5c9d0f54 100644
--- a/src/org/apache/xpath/Expression.java
+++ b/src/org/apache/xpath/Expression.java
@@ -56,11 +56,11 @@ public abstract class Expression implements java.io.Serializable, ExpressionNode
private ExpressionNode m_parent;
/**
- * XPath 3.1 support for, XPath.fixupVariables(..) action for
- * feature implementations like XPath function item, "for" expression.
+ * XPath 3.1 support for, XPath.fixupVariables(..) action for feature
+ * implementations like XPath function item, "for", "let", 'quantified'
+ * expressions.
*
- * XPath variable references, within these XPath 3.1 features are not
- * stored within XPath context's variable stack.
+ * We don't use, XalanJ XPath context's variable stack for this purpose.
*/
protected static List<QName> m_xpathVarList = new ArrayList<QName>();
diff --git a/src/org/apache/xpath/XPathContext.java b/src/org/apache/xpath/XPathContext.java
index 9221b7e0..71ac656d 100644
--- a/src/org/apache/xpath/XPathContext.java
+++ b/src/org/apache/xpath/XPathContext.java
@@ -133,7 +133,8 @@ public class XPathContext extends DTMManager // implements ExpressionContext
* We use this java.util.Map object, to store XPath 3.1 variable binding
* information (i.e, a mapping from variable name to its run-time value).
* These variable bindings, are used for XPath feature implementations
- * like function item, 'for' expression.
+ * like function item, "for", "let", 'quantified' expressions.
+ *
* We don't use, XalanJ XPath context's variable stack for this purpose.
*/
private Map<QName, XObject> xpathVarMap = new HashMap<QName, XObject>();
diff --git a/src/org/apache/xpath/compiler/Compiler.java b/src/org/apache/xpath/compiler/Compiler.java
index b3c2a2c8..67035d35 100644
--- a/src/org/apache/xpath/compiler/Compiler.java
+++ b/src/org/apache/xpath/compiler/Compiler.java
@@ -131,6 +131,8 @@ public class Compiler extends OpMap
expr = compile(opPos + 2); break;
case OpCodes.OP_FOR_EXPR :
expr = forExpr(opPos); break;
+ case OpCodes.OP_LET_EXPR :
+ expr = letExpr(opPos); break;
case OpCodes.OP_QUANTIFIED_EXPR :
expr = quantifiedExpr(opPos); break;
case OpCodes.OP_IF_EXPR :
@@ -1182,6 +1184,11 @@ private static final boolean DEBUG = false;
return XPathParser.fForExpr;
}
+ Expression letExpr(int opPos) throws TransformerException
+ {
+ return XPathParser.fLetExpr;
+ }
+
Expression quantifiedExpr(int opPos) throws TransformerException
{
return XPathParser.fQuantifiedExpr;
diff --git a/src/org/apache/xpath/compiler/Lexer.java b/src/org/apache/xpath/compiler/Lexer.java
index f185868f..80c9c7ad 100644
--- a/src/org/apache/xpath/compiler/Lexer.java
+++ b/src/org/apache/xpath/compiler/Lexer.java
@@ -629,39 +629,70 @@ class Lexer
{
uName = prefix;
}
-
+
+ // To handle XPath 3.1 "let" expression variable binding strings like
+ // $varName := val, otherwise the character ':' as part of symbol :=
+ // used for "let" expression variable binding shall be treated for
+ // XML namespace processing.
+ boolean isLetExprNsCheckOk = false;
+ if (((m_compiler.getTokenQueue()).indexOf("let") != -1))
+ {
+ if (":=".equals(pat.substring(posOfNSSep, posOfNSSep + 2)))
+ {
+ isLetExprNsCheckOk = true;
+ }
+ }
+
if ((null != uName) && (uName.length() > 0))
{
- addToTokenQueue(uName);
- addToTokenQueue(":");
-
- String s = pat.substring(posOfNSSep + 1, posOfScan);
-
- if (s.length() > 0)
- addToTokenQueue(s);
+ if (!isLetExprNsCheckOk)
+ {
+ addToTokenQueue(uName);
+ addToTokenQueue(":");
+
+ String s = pat.substring(posOfNSSep + 1, posOfScan);
+
+ if (s.length() > 0)
+ addToTokenQueue(s);
+ }
+ else
+ {
+ String xpathLetExprBindingVarNameStr = prefix;
+ if ("".equals(xpathLetExprBindingVarNameStr))
+ {
+ // handles XPath "let" expression variable binding strings like $varName := val
+ addToTokenQueue(":");
+ }
+ else
+ {
+ // handles XPath "let" expression variable binding strings like $varName:= val
+ addToTokenQueue(xpathLetExprBindingVarNameStr);
+ addToTokenQueue(":");
+ }
+ }
}
else
{
- // To older XPath code it doesn't matter if
- // error() is called or errorForDOM3().
- m_processor.errorForDOM3(XPATHErrorResources.ER_PREFIX_MUST_RESOLVE,
- new String[] {prefix}); //"Prefix must resolve to a namespace: {0}";
-
-/** old code commented out 17-Sep-2004
-// error("Could not locate namespace for prefix: "+prefix);
-// m_processor.error(XPATHErrorResources.ER_PREFIX_MUST_RESOLVE,
-// new String[] {prefix}); //"Prefix must resolve to a namespace: {0}";
-*/
-
- /*** Old code commented out 10-Jan-2001
- addToTokenQueue(prefix);
- addToTokenQueue(":");
-
- String s = pat.substring(posOfNSSep + 1, posOfScan);
-
- if (s.length() > 0)
- addToTokenQueue(s);
- ***/
+ if (isLetExprNsCheckOk)
+ {
+ String xpathLetExprBindingVarNameStr = prefix;
+ if ("".equals(xpathLetExprBindingVarNameStr))
+ {
+ // handles XPath "let" expression variable binding strings like $varName := val
+ addToTokenQueue(":");
+ }
+ else
+ {
+ // handles XPath "let" expression variable binding strings like $varName:= val
+ addToTokenQueue(xpathLetExprBindingVarNameStr);
+ addToTokenQueue(":");
+ }
+ }
+ else
+ {
+ m_processor.errorForDOM3(XPATHErrorResources.ER_PREFIX_MUST_RESOLVE,
+ new String[] {prefix}); //"Prefix must resolve to a namespace: {0}";
+ }
}
return -1;
diff --git a/src/org/apache/xpath/compiler/OpCodes.java b/src/org/apache/xpath/compiler/OpCodes.java
index 4377cc37..cabf00d4 100644
--- a/src/org/apache/xpath/compiler/OpCodes.java
+++ b/src/org/apache/xpath/compiler/OpCodes.java
@@ -682,11 +682,13 @@ public class OpCodes
public static final int OP_FOR_EXPR = 63;
- public static final int OP_QUANTIFIED_EXPR = 64;
+ public static final int OP_LET_EXPR = 64;
- public static final int OP_IF_EXPR = 65;
+ public static final int OP_QUANTIFIED_EXPR = 65;
+
+ public static final int OP_IF_EXPR = 66;
/** The next free ID. Please keep this up to date. */
- private static final int NEXT_FREE_ID = 66;
+ private static final int NEXT_FREE_ID = 67;
}
diff --git a/src/org/apache/xpath/compiler/XPathParser.java b/src/org/apache/xpath/compiler/XPathParser.java
index 5b127c06..2965598b 100644
--- a/src/org/apache/xpath/compiler/XPathParser.java
+++ b/src/org/apache/xpath/compiler/XPathParser.java
@@ -33,6 +33,8 @@ import org.apache.xpath.XPathProcessorException;
import org.apache.xpath.composite.ForExpr;
import org.apache.xpath.composite.ForQuantifiedExprVarBinding;
import org.apache.xpath.composite.IfExpr;
+import org.apache.xpath.composite.LetExpr;
+import org.apache.xpath.composite.LetExprVarBinding;
import org.apache.xpath.composite.QuantifiedExpr;
import org.apache.xpath.domapi.XPathStylesheetDOM3Exception;
import org.apache.xpath.functions.DynamicFunctionCall;
@@ -91,7 +93,8 @@ public class XPathParser
{"div", "or", "and", "mod", "to",
"eq", "ne", "lt", "gt", "le", "ge",
"for", "in", "return", "if", "then",
- "else", "some", "every", "satisfies", "-"};
+ "else", "some", "every", "satisfies",
+ "let", ":=", "-"};
private static final List<String> fXpathOpArrTokensList = Arrays.asList(XPATH_OP_ARR);
@@ -105,10 +108,12 @@ public class XPathParser
static ForExpr fForExpr = null;
- static IfExpr fIfExpr = null;
+ static LetExpr fLetExpr = null;
static QuantifiedExpr fQuantifiedExpr = null;
+ static IfExpr fIfExpr = null;
+
/**
* The parser constructor.
*/
@@ -895,6 +900,7 @@ public class XPathParser
* Expr ::= ExprSingle ("," ExprSingle)*
*
* ExprSingle ::= ForExpr
+ * | LetExpr
* | QuantifiedExpr
* | IfExpr
* | OrExpr
@@ -911,6 +917,14 @@ public class XPathParser
fForExpr = ForExpr(prevTokenStr);
}
+ else if (tokenIs("let")) {
+ // to check, whether XPath 'let' expression is a sub expression of another
+ // XPath expression (for e.g, a 'let' expression could be a function
+ // argument).
+ String prevTokenStr = getTokenRelative(-2);
+
+ fLetExpr = LetExpr(prevTokenStr);
+ }
else if (tokenIs("some")) {
// to check, whether XPath quantified 'some' expression is a sub expression
// of another XPath expression (for e.g, the 'some' expression could be a
@@ -1015,6 +1029,85 @@ public class XPathParser
return forExpr;
}
+ protected LetExpr LetExpr(String prevTokenStrBeforeLet) throws javax.xml.transform.TransformerException
+ {
+ int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
+
+ nextToken();
+
+ insertOp(opPos, 2, OpCodes.OP_LET_EXPR);
+
+ LetExpr letExpr = new LetExpr();
+
+ List<LetExprVarBinding> letExprVarBindingList = new ArrayList<LetExprVarBinding>();
+
+ while (!tokenIs("return") && m_token != null)
+ {
+ String bindingVarName = null;
+
+ if (letExprVarBindingList.size() > 0 && tokenIs(',')) {
+ nextToken();
+ }
+
+ if (tokenIs('$')) {
+ nextToken();
+ bindingVarName = m_token;
+ nextToken();
+ consumeExpected(":");
+ consumeExpected("=");
+ }
+
+ List<String> bindingXPathExprStrPartsList = new ArrayList<String>();
+
+ while (!(tokenIs(',') || tokenIs("return")) && m_token != null) {
+ bindingXPathExprStrPartsList.add(m_token);
+ nextToken();
+ }
+
+ String varBindingXpathStr = getXPathStrFromComponentParts(bindingXPathExprStrPartsList);
+
+ LetExprVarBinding letExprVarBinding = new LetExprVarBinding();
+ letExprVarBinding.setVarName(bindingVarName);
+ letExprVarBinding.setXpathExprStr(varBindingXpathStr);
+
+ letExprVarBindingList.add(letExprVarBinding);
+
+ if (tokenIs("return")) {
+ break;
+ }
+ }
+
+ consumeExpected("return");
+
+ List<String> xPathReturnExprStrPartsList = new ArrayList<String>();
+
+ while (m_token != null) {
+ if (tokenIs(')')) {
+ if ((getTokenRelative(0) == null) && "(".equals(prevTokenStrBeforeLet)) {
+ break;
+ }
+ else {
+ xPathReturnExprStrPartsList.add(m_token);
+ nextToken();
+ }
+ }
+ else {
+ xPathReturnExprStrPartsList.add(m_token);
+ nextToken();
+ }
+ }
+
+ String xPathReturnExprStr = getXPathStrFromComponentParts(xPathReturnExprStrPartsList);
+
+ letExpr.setLetExprVarBindingList(letExprVarBindingList);
+ letExpr.setReturnExprXPathStr(xPathReturnExprStr);
+
+ m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
+ m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
+
+ return letExpr;
+ }
+
protected QuantifiedExpr QuantifiedExpr(String prevTokenStrBeforeQuantifier, int quantifierExprType)
throws javax.xml.transform.TransformerException
{
diff --git a/src/org/apache/xpath/composite/ForExpr.java b/src/org/apache/xpath/composite/ForExpr.java
index a5d581e2..39582d03 100644
--- a/src/org/apache/xpath/composite/ForExpr.java
+++ b/src/org/apache/xpath/composite/ForExpr.java
@@ -178,7 +178,7 @@ public class ForExpr extends Expression {
// for each xdm item within sequence object 'xsObjResultSeq' (which is the
// result of variable binding xpath expression's evaluation), bind the 'for'
- // expression range variable in turn to that item.
+ // expression's binding variable in turn to that item.
for (int idx = 0; idx < xsObjResultSeq.size(); idx++) {
XObject xdmItem = xsObjResultSeq.item(idx);
diff --git a/src/org/apache/xpath/composite/LetExpr.java b/src/org/apache/xpath/composite/LetExpr.java
new file mode 100644
index 00000000..8d3868d4
--- /dev/null
+++ b/src/org/apache/xpath/composite/LetExpr.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.xpath.composite;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.xml.transform.SourceLocator;
+import javax.xml.transform.TransformerException;
+
+import org.apache.xml.utils.QName;
+import org.apache.xpath.Expression;
+import org.apache.xpath.ExpressionOwner;
+import org.apache.xpath.XPath;
+import org.apache.xpath.XPathContext;
+import org.apache.xpath.XPathVisitor;
+import org.apache.xpath.objects.ResultSequence;
+import org.apache.xpath.objects.XObject;
+
+/*
+ * The XalanJ XPath parser, creates and populates an object of this class,
+ * as a representation of XPath 3.1 "let" expression.
+ *
+ * The XPath 3.1 spec, defines "let" expression with following grammar,
+ *
+ * LetExpr ::= SimpleLetClause "return" ExprSingle
+ * SimpleLetClause ::= "let" SimpleLetBinding ("," SimpleLetBinding)*
+ * SimpleLetBinding ::= "$" VarName ":=" ExprSingle
+ *
+ * Ref : https://www.w3.org/TR/xpath-31/#id-let-expressions
+ *
+ * @author Mukul Gandhi <mu...@apache.org>
+ *
+ * @xsl.usage advanced
+ */
+public class LetExpr extends Expression {
+
+ private static final long serialVersionUID = 3063682088023616108L;
+
+ private List<LetExprVarBinding> fLetExprVarBindingList =
+ new ArrayList<LetExprVarBinding>();
+
+ private String fReturnExprXPathStr = null;
+
+ // the following two fields of this class, are used during
+ // XPath.fixupVariables(..) action as performed within object of
+ // this class.
+ private Vector fVars;
+ private int fGlobalsSize;
+
+ @Override
+ public void callVisitors(ExpressionOwner owner, XPathVisitor visitor) {
+ // no op
+ }
+
+ @Override
+ public XObject execute(XPathContext xctxt) throws TransformerException {
+
+ XObject evalResult = null;
+
+ SourceLocator srcLocator = xctxt.getSAXLocator();
+
+ int contextNode = xctxt.getContextNode();
+
+ Map<QName, XObject> xpathVarMap = xctxt.getXPathVarMap();
+
+ for (int idx = 0; idx < fLetExprVarBindingList.size(); idx++) {
+ LetExprVarBinding letExprVarBinding = fLetExprVarBindingList.get(idx);
+ String varName = letExprVarBinding.getVarName();
+ String fXpathExprStr = letExprVarBinding.getXpathExprStr();
+
+ XPath letExprVarBindingXpath = new XPath(fXpathExprStr, srcLocator, null,
+ XPath.SELECT, null);
+ if (fVars != null) {
+ letExprVarBindingXpath.fixupVariables(fVars, fGlobalsSize);
+ }
+
+ XObject varBindingEvalResult = letExprVarBindingXpath.execute(xctxt, contextNode,
+ null);
+
+ m_xpathVarList.add(new QName(varName));
+ xpathVarMap.put(new QName(varName), varBindingEvalResult);
+ }
+
+ XPath returnExprXpath = new XPath(fReturnExprXPathStr, srcLocator, null,
+ XPath.SELECT, null);
+
+ if (fVars != null) {
+ returnExprXpath.fixupVariables(fVars, fGlobalsSize);
+ }
+
+ evalResult = returnExprXpath.execute(xctxt, contextNode, null);
+
+ if (evalResult == null) {
+ // return an empty sequence, here
+ evalResult = new ResultSequence();
+ }
+
+ return evalResult;
+ }
+
+ @Override
+ public void fixupVariables(Vector vars, int globalsSize) {
+ fVars = (Vector)(vars.clone());
+ fGlobalsSize = globalsSize;
+ }
+
+ @Override
+ public boolean deepEquals(Expression expr) {
+ return false;
+ }
+
+ public List<LetExprVarBinding> getLetExprVarBindingList() {
+ return fLetExprVarBindingList;
+ }
+
+ public void setLetExprVarBindingList(List<LetExprVarBinding> fLetExprVarBindingList) {
+ this.fLetExprVarBindingList = fLetExprVarBindingList;
+ }
+
+ public String getReturnExprXPathStr() {
+ return fReturnExprXPathStr;
+ }
+
+ public void setReturnExprXPathStr(String fReturnExprXPathStr) {
+ this.fReturnExprXPathStr = fReturnExprXPathStr;
+ }
+
+}
diff --git a/src/org/apache/xpath/composite/LetExprVarBinding.java b/src/org/apache/xpath/composite/LetExprVarBinding.java
new file mode 100644
index 00000000..0e555b33
--- /dev/null
+++ b/src/org/apache/xpath/composite/LetExprVarBinding.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.xpath.composite;
+
+/*
+ * An object of this class, is used to store information about
+ * XPath 3.1 "let" expression's single variable binding (i.e,
+ * run-time information details related to the grammar fragment
+ * "$" VarName ":=" ExprSingle for a particular XPath "let"
+ * expression that's currently been evaluated).
+ *
+ * @author Mukul Gandhi <mu...@apache.org>
+ *
+ * @xsl.usage advanced
+ */
+public class LetExprVarBinding {
+
+ private String fVarName = null;
+
+ private String fXpathExprStr = null;
+
+ public String getVarName() {
+ return fVarName;
+ }
+
+ public void setVarName(String varName) {
+ this.fVarName = varName;
+ }
+
+ public String getXpathExprStr() {
+ return fXpathExprStr;
+ }
+
+ public void setXpathExprStr(String xpathExprStr) {
+ this.fXpathExprStr = xpathExprStr;
+ }
+
+}
diff --git a/src/org/apache/xpath/functions/DynamicFunctionCall.java b/src/org/apache/xpath/functions/DynamicFunctionCall.java
index c0b974f8..659dcb78 100644
--- a/src/org/apache/xpath/functions/DynamicFunctionCall.java
+++ b/src/org/apache/xpath/functions/DynamicFunctionCall.java
@@ -24,8 +24,10 @@ import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;
import org.apache.xalan.extensions.ExpressionContext;
+import org.apache.xalan.templates.StylesheetRoot;
import org.apache.xml.utils.QName;
import org.apache.xpath.Expression;
+import org.apache.xpath.ExpressionNode;
import org.apache.xpath.ExpressionOwner;
import org.apache.xpath.XPath;
import org.apache.xpath.XPathContext;
@@ -87,11 +89,36 @@ public class DynamicFunctionCall extends Expression {
int contextNode = xctxt.getContextNode();
- ExpressionContext exprContext = xctxt.getExpressionContext();
+ Map<QName, XObject> inlineFunctionVarMap = xctxt.getXPathVarMap();
- XObject functionRef = exprContext.getVariableOrParam(new QName(funcRefVarName));
+ // we get below reference of an XPath inline function, that this dynamic
+ // function call refers to.
- if (functionRef instanceof InlineFunction) {
+ XObject functionRef = inlineFunctionVarMap.get(new QName(funcRefVarName));
+
+ if (functionRef == null) {
+ ExpressionContext exprContext = xctxt.getExpressionContext();
+
+ try {
+ functionRef = exprContext.getVariableOrParam(new QName(funcRefVarName));
+ }
+ catch (TransformerException ex) {
+ // try to get an XPath inline function reference, within stylesheet's
+ // global scope.
+ ExpressionNode expressionNode = getExpressionOwner();
+ ExpressionNode stylesheetRootNode = null;
+ while (expressionNode != null) {
+ stylesheetRootNode = expressionNode;
+ expressionNode = expressionNode.exprGetParent();
+ }
+ StylesheetRoot stylesheetRoot = (StylesheetRoot)stylesheetRootNode;
+ Map<QName, InlineFunction> globalInlineFunctionVarMap = stylesheetRoot.
+ getInlineFunctionVarMap();
+ functionRef = globalInlineFunctionVarMap.get(new QName(funcRefVarName));
+ }
+ }
+
+ if ((functionRef != null) && (functionRef instanceof InlineFunction)) {
InlineFunction inlineFunction = (InlineFunction)functionRef;
String inlineFnXpathStr = inlineFunction.getFuncBodyXPathExprStr();
@@ -103,8 +130,6 @@ public class DynamicFunctionCall extends Expression {
+ "Number of arguments provided " + argList.size() + ".", xctxt.getSAXLocator());
}
- Map<QName, XObject> inlineFunctionVarMap = xctxt.getXPathVarMap();
-
for (int idx = 0; idx < funcParamNameList.size(); idx++) {
String funcParamName = funcParamNameList.get(idx);
@@ -124,7 +149,12 @@ public class DynamicFunctionCall extends Expression {
inlineFunctionVarMap.clear();
}
-
+ else {
+ throw new javax.xml.transform.TransformerException("XPST0008 variable '" + funcRefVarName + "' has "
+ + "not been declared (or its declaration is not in scope).",
+ xctxt.getSAXLocator());
+ }
+
return evalResult;
}
diff --git a/src/org/apache/xpath/operations/Variable.java b/src/org/apache/xpath/operations/Variable.java
index 41f13ceb..8ca1185d 100644
--- a/src/org/apache/xpath/operations/Variable.java
+++ b/src/org/apache/xpath/operations/Variable.java
@@ -144,7 +144,7 @@ public class Variable extends Expression implements PathComponent
if (m_xpathVarList.contains(m_qname)) {
// this takes care of, variable references within, XPath 3.1 feature implementations
- // like function item, "for" expression.
+ // like function item, "for", "let", 'quantified' expressions.
return;
}
diff --git a/tests/let_expr/gold/test1.out b/tests/let_expr/gold/test1.out
new file mode 100644
index 00000000..885dfd7e
--- /dev/null
+++ b/tests/let_expr/gold/test1.out
@@ -0,0 +1 @@
+area = 78.5
\ No newline at end of file
diff --git a/tests/let_expr/gold/test2.out b/tests/let_expr/gold/test2.out
new file mode 100644
index 00000000..9477eff5
--- /dev/null
+++ b/tests/let_expr/gold/test2.out
@@ -0,0 +1 @@
+(a) Boston employee count : 2 (b) Vienna employee count : 3
\ No newline at end of file
diff --git a/tests/let_expr/test1.xsl b/tests/let_expr/test1.xsl
new file mode 100644
index 00000000..54eb0e97
--- /dev/null
+++ b/tests/let_expr/test1.xsl
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="3.0">
+
+ <!-- Author: mukulg@apache.org -->
+
+ <!-- An XSLT stylesheet test, for the XPath 3.1 "let" expression.
+
+ This XSLT stylesheet, borrows an XPath "let" expression example
+ from https://www.altova.com/. -->
+
+ <xsl:output method="text"/>
+
+ <xsl:template match="/">
+ <xsl:value-of select="let $r := 5,
+ $pi := 3.14
+ return concat('area = ', $pi * ($r * $r))"/>
+ </xsl:template>
+
+ <!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/tests/let_expr/test1_a.xml b/tests/let_expr/test1_a.xml
new file mode 100644
index 00000000..a5b5bc92
--- /dev/null
+++ b/tests/let_expr/test1_a.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<company>
+ <office location="Boston">
+ <employee>
+ <first_name>John</first_name>
+ <last_name>Smith</last_name>
+ <age>25</age>
+ </employee>
+ <employee>
+ <first_name>John</first_name>
+ <last_name>Jones</last_name>
+ <age>30</age>
+ </employee>
+ </office>
+ <office location="Vienna">
+ <employee>
+ <first_name>Mary</first_name>
+ <last_name>Brown</last_name>
+ <age>30</age>
+ </employee>
+ <employee>
+ <first_name>Peter</first_name>
+ <last_name>Davis</last_name>
+ <age>34</age>
+ </employee>
+ <employee>
+ <first_name>Mark</first_name>
+ <last_name>Mason</last_name>
+ <age>44</age>
+ </employee>
+ </office>
+</company>
+
diff --git a/tests/let_expr/test2.xsl b/tests/let_expr/test2.xsl
new file mode 100644
index 00000000..8bbc8edc
--- /dev/null
+++ b/tests/let_expr/test2.xsl
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="3.0">
+
+ <!-- Author: mukulg@apache.org -->
+
+ <!-- An XSLT stylesheet test, for the XPath 3.1 "let" expression.
+
+ This XSLT stylesheet, borrows an XPath "let" expression example
+ from https://www.altova.com/, with slight modifications.
+
+ Within this XSLT stylesheet, an XPath "let" expression binds two
+ literal values to the '$r' and '$pi' variables respectively. It also binds
+ a third variable '$area' to an inline function. The return expression calls
+ an inline function by using its variable reference '$area' and passing
+ the '$r' variable as an argument to it. -->
+
+ <xsl:output method="text"/>
+
+ <xsl:template match="/">
+ <xsl:variable name="area" select="let $pi := 3.14,
+ $area := function ($arg) { ($pi * $arg * $arg) },
+ $r := 5
+ return $area($r)"/>
+ <xsl:value-of select="concat('area = ', $area)"/>
+ </xsl:template>
+
+ <!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/tests/let_expr/test3.xsl b/tests/let_expr/test3.xsl
new file mode 100644
index 00000000..fb97834d
--- /dev/null
+++ b/tests/let_expr/test3.xsl
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="3.0">
+
+ <!-- Author: mukulg@apache.org -->
+
+ <!-- use with test1_a.xml -->
+
+ <!-- An XSLT stylesheet test, for the XPath 3.1 "let" expression.
+
+ This XSLT stylesheet, borrows an XPath "let" expression example
+ from https://www.altova.com/, with slight modifications. -->
+
+ <xsl:output method="text"/>
+
+ <xsl:template match="/company">
+ <xsl:variable name="officeDataInfo" select="let $x := office[@location = 'Boston'],
+ $y := office[@location = 'Vienna']
+ return concat('(a) Boston employee count : ', count($x/employee),
+ ' (b) Vienna employee count : ', count($y/employee))"/>
+ <xsl:value-of select="$officeDataInfo"/>
+ </xsl:template>
+
+ <!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/tests/org/apache/xalan/xpath3/LetExprTests.java b/tests/org/apache/xalan/xpath3/LetExprTests.java
new file mode 100644
index 00000000..644e38c1
--- /dev/null
+++ b/tests/org/apache/xalan/xpath3/LetExprTests.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.xalan.xpath3;
+
+import org.apache.xalan.util.XslTransformTestsUtil;
+import org.apache.xalan.xslt3.XSLConstants;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * XPath 3.1 test cases, for the "let" expression.
+ *
+ * @author Mukul Gandhi <mu...@apache.org>
+ *
+ * @xsl.usage advanced
+ */
+public class LetExprTests extends XslTransformTestsUtil {
+
+ private static final String XSL_TRANSFORM_INPUT_DIRPATH = XSLConstants.XSL_TRANSFORM_INPUT_DIRPATH_PREFIX + "let_expr/";
+
+ private static final String XSL_TRANSFORM_GOLD_DIRPATH = XSLConstants.XSL_TRANSFORM_GOLD_DIRPATH_PREFIX + "let_expr/gold/";
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ // no op
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ xmlDocumentBuilderFactory = null;
+ xmlDocumentBuilder = null;
+ xslTransformerFactory = null;
+ }
+
+ @Test
+ public void xslLetExprTest1() {
+ String xmlFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test1.xsl";
+ String xslFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test1.xsl";
+
+ String goldFilePath = XSL_TRANSFORM_GOLD_DIRPATH + "test1.out";
+
+ runXslTransformAndAssertOutput(xmlFilePath, xslFilePath, goldFilePath, null);
+ }
+
+ @Test
+ public void xslLetExprTest2() {
+ String xmlFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test2.xsl";
+ String xslFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test2.xsl";
+
+ String goldFilePath = XSL_TRANSFORM_GOLD_DIRPATH + "test1.out";
+
+ runXslTransformAndAssertOutput(xmlFilePath, xslFilePath, goldFilePath, null);
+ }
+
+ @Test
+ public void xslLetExprTest3() {
+ String xmlFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test1_a.xml";
+ String xslFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test3.xsl";
+
+ String goldFilePath = XSL_TRANSFORM_GOLD_DIRPATH + "test2.out";
+
+ runXslTransformAndAssertOutput(xmlFilePath, xslFilePath, goldFilePath, null);
+ }
+
+}
diff --git a/tests/org/apache/xalan/xslt3/AllXsl3Tests.java b/tests/org/apache/xalan/xslt3/AllXsl3Tests.java
index b6196a2a..b86c9063 100644
--- a/tests/org/apache/xalan/xslt3/AllXsl3Tests.java
+++ b/tests/org/apache/xalan/xslt3/AllXsl3Tests.java
@@ -27,6 +27,7 @@ import org.apache.xalan.xpath3.FnUnparsedTextTests;
import org.apache.xalan.xpath3.ForExprTests;
import org.apache.xalan.xpath3.IfExprTests;
import org.apache.xalan.xpath3.InlineFunctionItemExprTests;
+import org.apache.xalan.xpath3.LetExprTests;
import org.apache.xalan.xpath3.QuantifiedExprTests;
import org.apache.xalan.xpath3.RangeExprTests;
import org.apache.xalan.xpath3.SequenceTests;
@@ -58,7 +59,7 @@ import org.junit.runners.Suite.SuiteClasses;
W3c_xslt30_IterateTests.class, W3c_xslt30_AxesTests.class, XslIterateTests.class,
ValueComparisonTests.class, InlineFunctionItemExprTests.class, FnForEachTests.class,
FnFilterTests.class, DynamicFunctionCallTests.class, IfExprTests.class,
- ForExprTests.class })
+ ForExprTests.class, LetExprTests.class })
public class AllXsl3Tests {
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@xalan.apache.org
For additional commands, e-mail: commits-help@xalan.apache.org