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