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 2024/06/06 13:48:27 UTC

(xalan-java) branch xalan-j_xslt3.0 updated: committing implementation of xpath3.1 map:merge function. this is wip.

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 61986d79 committing implementation of xpath3.1 map:merge function. this is wip.
61986d79 is described below

commit 61986d79a1c87fb0ad9809ab2ca5905363af2e3d
Author: Mukul Gandhi <ga...@gmail.com>
AuthorDate: Thu Jun 6 19:18:13 2024 +0530

    committing implementation of xpath3.1 map:merge function. this is wip.
---
 src/org/apache/xpath/compiler/FunctionTable.java   |  60 ++--
 src/org/apache/xpath/compiler/Keywords.java        |   3 +
 src/org/apache/xpath/compiler/XPathParserImpl.java | 180 ++++++-----
 .../apache/xpath/functions/map/FuncMapMerge.java   | 345 +++++++++++++++++++++
 4 files changed, 477 insertions(+), 111 deletions(-)

diff --git a/src/org/apache/xpath/compiler/FunctionTable.java b/src/org/apache/xpath/compiler/FunctionTable.java
index 9dc27844..3ea023ff 100644
--- a/src/org/apache/xpath/compiler/FunctionTable.java
+++ b/src/org/apache/xpath/compiler/FunctionTable.java
@@ -483,6 +483,9 @@ public class FunctionTable
   
   /** The 'array:for-each-pair()' id. */
   public static final int FUNC_ARRAY_FOR_EACH_PAIR = 155;
+  
+  /** The 'map:merge()' id. */
+  public static final int FUNC_MAP_MERGE = 156;
 
   // Proprietary
 
@@ -518,6 +521,21 @@ public class FunctionTable
   
   static final List<Integer> XPATH_MATH_FUNC_IDS_ARR = Arrays.asList(XPATH_MATH_FUNC_IDS);
   
+  /**
+   * XPath 3.1 built-in functions namespace uri, for map related functions. The XPath functions available 
+   * within this namespace, must be used by qualifying the function name with an XML namespace bound to this 
+   * uri (the commonly used XML namespace prefix for this namespace uri is "map", as specified by 
+   * XPath 3.1 spec).
+   */
+  public static final String XPATH_BUILT_IN_MAP_FUNCS_NS_URI = "http://www.w3.org/2005/xpath-functions/map";
+    
+  static final Integer[] XPATH_MAP_FUNC_IDS = new Integer[] { new Integer(FUNC_MAP_MERGE), new Integer(FUNC_MAP_SIZE), 
+		                                                      new Integer(FUNC_MAP_KEYS), new Integer(FUNC_MAP_CONTAINS), 
+		                                                      new Integer(FUNC_MAP_GET), new Integer(FUNC_MAP_PUT), 
+		                                                      new Integer(FUNC_MAP_ENTRY), new Integer(FUNC_MAP_FOREACH) };
+  
+  static final List<Integer> XPATH_MAP_FUNC_IDS_ARR = Arrays.asList(XPATH_MAP_FUNC_IDS);
+  
   /**
    * XPath 3.1 built-in functions namespace uri, for array related functions. The XPath functions available 
    * within this namespace, must be used by qualifying the function name with an XML namespace bound to this 
@@ -535,21 +553,6 @@ public class FunctionTable
 		                                                        new Integer(FUNC_ARRAY_FILTER), new Integer(FUNC_ARRAY_FOR_EACH_PAIR) };
   
   static final List<Integer> XPATH_ARRAY_FUNC_IDS_ARR = Arrays.asList(XPATH_ARRAY_FUNC_IDS);
-  
-  /**
-   * XPath 3.1 built-in functions namespace uri, for map related functions. The XPath functions available 
-   * within this namespace, must be used by qualifying the function name with an XML namespace bound to this 
-   * uri (the commonly used XML namespace prefix for this namespace uri is "map", as specified by 
-   * XPath 3.1 spec).
-   */
-  public static final String XPATH_BUILT_IN_MAP_FUNCS_NS_URI = "http://www.w3.org/2005/xpath-functions/map";
-    
-  static final Integer[] XPATH_MAP_FUNC_IDS = new Integer[] { new Integer(FUNC_MAP_SIZE), new Integer(FUNC_MAP_KEYS),
-		                                                                         new Integer(FUNC_MAP_CONTAINS), new Integer(FUNC_MAP_GET), 
-		                                                                         new Integer(FUNC_MAP_PUT), new Integer(FUNC_MAP_ENTRY),
-		                                                                         new Integer(FUNC_MAP_FOREACH) };
-  
-  static final List<Integer> XPATH_MAP_FUNC_IDS_ARR = Arrays.asList(XPATH_MAP_FUNC_IDS);
 
   /**
    * The function table.
@@ -573,7 +576,7 @@ public class FunctionTable
    * Number of built in functions. Be sure to update this as
    * built-in functions are added.
    */
-  private static final int NUM_BUILT_IN_FUNCS = 156;
+  private static final int NUM_BUILT_IN_FUNCS = 157;
 
   /**
    * Number of built-in functions that may be added.
@@ -809,7 +812,18 @@ public class FunctionTable
     
     m_functions[FUNC_DEFAULT_COLLATION] = org.apache.xpath.functions.FuncDefaultCollation.class;
     m_functions[FUNC_BASE_URI] = org.apache.xpath.functions.FuncBaseUri.class;
-    m_functions[FUNC_DOCUMENT_URI] = org.apache.xpath.functions.FuncDocumentUri.class;    
+    m_functions[FUNC_DOCUMENT_URI] = org.apache.xpath.functions.FuncDocumentUri.class;        
+    
+    // XPath 3.1 built-in functions configurations for the map 
+    // functions namespace http://www.w3.org/2005/xpath-functions/map
+    m_functions[FUNC_MAP_MERGE] = org.apache.xpath.functions.map.FuncMapMerge.class;
+    m_functions[FUNC_MAP_SIZE] = org.apache.xpath.functions.map.FuncMapSize.class;
+    m_functions[FUNC_MAP_KEYS] = org.apache.xpath.functions.map.FuncMapKeys.class;
+    m_functions[FUNC_MAP_CONTAINS] = org.apache.xpath.functions.map.FuncMapContains.class;
+    m_functions[FUNC_MAP_GET] = org.apache.xpath.functions.map.FuncMapGet.class;
+    m_functions[FUNC_MAP_PUT] = org.apache.xpath.functions.map.FuncMapPut.class;
+    m_functions[FUNC_MAP_ENTRY] = org.apache.xpath.functions.map.FuncMapEntry.class;
+    m_functions[FUNC_MAP_FOREACH] = org.apache.xpath.functions.map.FuncMapForEach.class;
     
     // XPath 3.1 built-in functions configurations for the array 
     // functions namespace http://www.w3.org/2005/xpath-functions/array    
@@ -828,16 +842,6 @@ public class FunctionTable
     m_functions[FUNC_ARRAY_FILTER] = org.apache.xpath.functions.array.FuncArrayFilter.class;
     m_functions[FUNC_ARRAY_FOR_EACH_PAIR] = org.apache.xpath.functions.array.FuncArrayForEachPair.class;
     
-    // XPath 3.1 built-in functions configurations for the map 
-    // functions namespace http://www.w3.org/2005/xpath-functions/map    
-    m_functions[FUNC_MAP_SIZE] = org.apache.xpath.functions.map.FuncMapSize.class;
-    m_functions[FUNC_MAP_KEYS] = org.apache.xpath.functions.map.FuncMapKeys.class;
-    m_functions[FUNC_MAP_CONTAINS] = org.apache.xpath.functions.map.FuncMapContains.class;
-    m_functions[FUNC_MAP_GET] = org.apache.xpath.functions.map.FuncMapGet.class;
-    m_functions[FUNC_MAP_PUT] = org.apache.xpath.functions.map.FuncMapPut.class;
-    m_functions[FUNC_MAP_ENTRY] = org.apache.xpath.functions.map.FuncMapEntry.class;
-    m_functions[FUNC_MAP_FOREACH] = org.apache.xpath.functions.map.FuncMapForEach.class;
-    
     m_functions[FUNC_PARSE_JSON] = org.apache.xpath.functions.FuncParseJson.class;
     
   }
@@ -1101,6 +1105,8 @@ public class FunctionTable
          
          // XPath 3.1 functions configurations for the map functions 
          // namespace http://www.w3.org/2005/xpath-functions/map
+         m_functionID.put(Keywords.FUNC_MAP_MERGE,
+                          new Integer(FunctionTable.FUNC_MAP_MERGE));
          m_functionID.put(Keywords.FUNC_MAP_SIZE,
                           new Integer(FunctionTable.FUNC_MAP_SIZE));
          m_functionID.put(Keywords.FUNC_MAP_KEYS,
diff --git a/src/org/apache/xpath/compiler/Keywords.java b/src/org/apache/xpath/compiler/Keywords.java
index e3262c3d..b8cdee8a 100644
--- a/src/org/apache/xpath/compiler/Keywords.java
+++ b/src/org/apache/xpath/compiler/Keywords.java
@@ -566,6 +566,9 @@ public class Keywords
   /** in-scope-prefixes function string. */
   public static final String FUNC_IN_SCOPE_PREFIXES = "in-scope-prefixes";
   
+  /** map:merge function string. */
+  public static final String FUNC_MAP_MERGE = "merge";
+  
   /** map:size function string. */
   public static final String FUNC_MAP_SIZE = "size";
   
diff --git a/src/org/apache/xpath/compiler/XPathParserImpl.java b/src/org/apache/xpath/compiler/XPathParserImpl.java
index 54105c2d..5801e879 100644
--- a/src/org/apache/xpath/compiler/XPathParserImpl.java
+++ b/src/org/apache/xpath/compiler/XPathParserImpl.java
@@ -1124,6 +1124,9 @@ public class XPathParserImpl
 		Object id = null;
 		
 		switch (key) {
+		      case "merge":
+		        id = FunctionTable.FUNC_MAP_MERGE;
+		        break;
 		      case "size":
 			     id = FunctionTable.FUNC_MAP_SIZE;
 			     break;
@@ -1281,7 +1284,7 @@ public class XPathParserImpl
         	 consumeExpected('{'); 
           }
           
-          List<String> sequenceConstructorXPathParts = new ArrayList<String>();
+          List<String> xpathExprParts = new ArrayList<String>();
           
           if (isSequenceConstructor && tokenIs(')') && (getTokenRelative(0) == null)) {
               // The XPath expression is ()
@@ -1292,11 +1295,11 @@ public class XPathParserImpl
               
               insertOp(opPos, 2, OpCodes.OP_SEQUENCE_CONSTRUCTOR_EXPR);
               
-              sequenceConstructorXPathParts.add(XPATH_EXPR_STR_EMPTY_SEQUENCE);
+              xpathExprParts.add(XPATH_EXPR_STR_EMPTY_SEQUENCE);
               
               fXPathSequenceConstructor = new XPathSequenceConstructor();              
               fXPathSequenceConstructor.setSequenceConstructorXPathParts(
-                                                                    sequenceConstructorXPathParts);
+                                                                    xpathExprParts);
               
               m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH, 
             		                                 m_ops.getOp(OpMap.MAPINDEX_LENGTH) - opPos);
@@ -1304,8 +1307,8 @@ public class XPathParserImpl
               return;
           }          
           else if (((isSquareArrayConstructor && tokenIs(']')) || (isCurlyArrayConstructor && tokenIs('}'))) && 
-        		  (getTokenRelative(0) == null)) {
-             // The XPath expression is []
+        		                                                                        (getTokenRelative(0) == null)) {
+             // The XPath expression is [], or {}
         	 
              int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
               
@@ -1324,61 +1327,57 @@ public class XPathParserImpl
           
           while (m_token != null) {                            
               if (tokenIs("function")) {
-                 // XPath expression parse from here, till the token '}' shall get 
-                 // an 'inline function expression' string. The XPath expression
-                 // parse here, can use the token ',' as part of 'inline function 
-                 // expression' (for e.g, to specify more than one function parameter
-                 // for the function item).
-                 List<String> seqItemXPathStrPartsList = new ArrayList<String>();
+                 // Trying to parse an entire XPath expression string as 
+            	 // 'inline function expression'.
+                 List<String> inlineFuncBodyXPathStrPartsList = new ArrayList<String>();
                  
                  while (!tokenIs('}') && m_token != null) {
                     if (getTokenRelative(0) != null) {
-                       seqItemXPathStrPartsList.add(m_token);
+                       inlineFuncBodyXPathStrPartsList.add(m_token);
                     }
                     nextToken();
                  }
+                 
                  if (tokenIs('}')) {
-                    seqItemXPathStrPartsList.add(m_token);
+                    inlineFuncBodyXPathStrPartsList.add(m_token);
                     nextToken();
                     if (tokenIs(',')) {
                        nextToken();    
                     }
                  }
                  
-                 if (seqItemXPathStrPartsList.size() > 0) {
-                    String seqItemXPathExprStr = getXPathStrFromComponentParts(
-                                                                       seqItemXPathStrPartsList);                 
-                    sequenceConstructorXPathParts.add(seqItemXPathExprStr);
+                 if (inlineFuncBodyXPathStrPartsList.size() > 0) {
+                    String xpathExprStr = getXPathStrFromComponentParts(inlineFuncBodyXPathStrPartsList);                 
+                    xpathExprParts.add(xpathExprStr);
                  }
               }
               else {
                  // XPath expression parse from here, till the token ',' (which we assume 
-                 // here, as an XPath sequence constructor's operand delimiter) shall get
+                 // here, as an XPath sequence/array constructor's member delimiter) shall get
                  // an XPath sub-expression that represents anything else than an XPath
                  // 'inline function expression'.
-                 List<String> seqItemXPathStrPartsList = new ArrayList<String>();
-                  
-                 while (!tokenIs(",") && m_token != null) {
+                 List<String> seqOrArrayMemberXPathStrPartsList = new ArrayList<String>();                 
+                 
+                 while (!tokenIs(",") && (m_token != null)) {
                     if (getTokenRelative(0) != null) {
-                       seqItemXPathStrPartsList.add(m_token);
+                       seqOrArrayMemberXPathStrPartsList.add(m_token);
                     }
                     nextToken();
                  }
                  
-                 if (seqItemXPathStrPartsList.size() > 0) {
-                    String seqItemXPathExprStr = getXPathStrFromComponentParts(seqItemXPathStrPartsList);
-                    if (isSequenceConstructor && seqItemXPathExprStr.endsWith(")")) {
-                        seqItemXPathExprStr = seqItemXPathExprStr.substring(0, seqItemXPathExprStr.length());    
+                 if (seqOrArrayMemberXPathStrPartsList.size() > 0) {
+                    String seqOrArrayMemeberXPathExprStr = getXPathStrFromComponentParts(seqOrArrayMemberXPathStrPartsList);
+                    if (isSequenceConstructor && seqOrArrayMemeberXPathExprStr.endsWith(")")) {
+                        seqOrArrayMemeberXPathExprStr = seqOrArrayMemeberXPathExprStr.substring(0, seqOrArrayMemeberXPathExprStr.length());    
                     }
                     
-                    boolean xpathExprCompletesLiteralArray = (isSquareArrayConstructor && seqItemXPathExprStr.endsWith("]")) || 
-                    		                                 (isCurlyArrayConstructor && seqItemXPathExprStr.endsWith("}"));
+                    boolean xpathExprCompletesLiteralArray = (isSquareArrayConstructor && seqOrArrayMemeberXPathExprStr.endsWith("]")) || 
+                    		                                 (isCurlyArrayConstructor && seqOrArrayMemeberXPathExprStr.endsWith("}"));
                     if (xpathExprCompletesLiteralArray) {
-                        seqItemXPathExprStr = seqItemXPathExprStr.substring(0, 
-                                                                       seqItemXPathExprStr.length());    
+                        seqOrArrayMemeberXPathExprStr = seqOrArrayMemeberXPathExprStr.substring(0, seqOrArrayMemeberXPathExprStr.length());    
                     }
                     
-                    sequenceConstructorXPathParts.add(seqItemXPathExprStr);
+                    xpathExprParts.add(seqOrArrayMemeberXPathExprStr);
                  }
                  
                  if (m_token != null) {
@@ -1387,12 +1386,12 @@ public class XPathParserImpl
               }                            
           }
           
-          boolean isXPathExprOkToProceed = true;
+          boolean isXPathParseOkToProceed = true;
           
-          // Check that each, string within the list 'sequenceConstructorXPathParts' 
-          // has balanced parentheses pairs '(' and ')'.
-          for (int idx = 0; idx < sequenceConstructorXPathParts.size(); idx++) {
-             String seqItemXPathExprStr = sequenceConstructorXPathParts.get(idx);
+          // Verify that, each xpath string member within the list 
+          // 'xpathExprParts' has balanced parentheses pairs.
+          for (int idx = 0; idx < xpathExprParts.size(); idx++) {
+             String seqOrArrayMemberXPathExprStr = xpathExprParts.get(idx);
              char lParenChar = '(';
              char rParenChar = ')';
              if (isSequenceConstructor) {
@@ -1408,38 +1407,39 @@ public class XPathParserImpl
             	 rParenChar = '}'; 
              }
              
-             boolean isStrHasBalancedParentheses = isStrHasBalancedParentheses(seqItemXPathExprStr, lParenChar, rParenChar);
+             boolean isStrHasBalancedParentheses = isStrHasBalancedParentheses(seqOrArrayMemberXPathExprStr, 
+            		                                                                                     lParenChar, rParenChar);
              if (!isStrHasBalancedParentheses) {
-                isXPathExprOkToProceed = false;
+                isXPathParseOkToProceed = false;
                 break;
              }
           }
           
-          // To check, whether sequence with at-least two xdm items may be constructed
-	      if (isSequenceConstructor && !(isXPathExprOkToProceed && (sequenceConstructorXPathParts.size() > 1))) {
-	         isXPathExprOkToProceed = false;    
+          // Verifying, whether sequence constructor expression, has at-least two 
+          // xpath expression members.          
+	      // (Sequence constructor expressions having single '(' & ')' parentheses, 
+          // are evaluated with Xalan-J parser legacy op code OpCodes.OP_GROUP from 
+          // within the Xalan-J parse method call ExprSingle())
+	      if (isXPathParseOkToProceed && isSequenceConstructor) {
+	    	 if (!(xpathExprParts.size() > 1)) {
+	    		isXPathParseOkToProceed = false; 
+	    	 }
 	      }
-          
-          // Upto here within this method, we've analyzed an given XPath input string 
-          // completely by traversing its sequence of XPath tokens. We can now choose from one of 
-          // following two options to continue with this XPath expression parse.          
-          if (isXPathExprOkToProceed) {
-        	  // Here we've a valid XPath parse state to continue further
+                             
+          if (isXPathParseOkToProceed) {
               int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
               
               nextToken();
               
               if (isSquareArrayConstructor || isCurlyArrayConstructor) {
             	 insertOp(opPos, 2, OpCodes.OP_ARRAY_CONSTRUCTOR_EXPR);
-            	 fXPathArrayConstructor = new XPathArrayConstructor();              
-            	 fXPathArrayConstructor.setArrayConstructorXPathParts(
-                                                                sequenceConstructorXPathParts);
+            	 fXPathArrayConstructor = new XPathArrayConstructor();            	 
+            	 fXPathArrayConstructor.setArrayConstructorXPathParts(xpathExprParts);
               }
               else {
                  insertOp(opPos, 2, OpCodes.OP_SEQUENCE_CONSTRUCTOR_EXPR);
-                 fXPathSequenceConstructor = new XPathSequenceConstructor();              
-                 fXPathSequenceConstructor.setSequenceConstructorXPathParts(
-                                                                sequenceConstructorXPathParts);
+                 fXPathSequenceConstructor = new XPathSequenceConstructor();                 
+                 fXPathSequenceConstructor.setSequenceConstructorXPathParts(xpathExprParts);
               }
               
               m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH,
@@ -1447,10 +1447,10 @@ public class XPathParserImpl
              
           }
           else {
-             // Re-start XPath parse, reusing the same token queue             
-             m_queueMark = 0;
-             nextToken();
-             ExprSingle();
+              // Re-start XPath parse, reusing the same token queue             
+              m_queueMark = 0;
+              nextToken();
+              ExprSingle();
           }
       }
       else if (fIsBeginParse && tokenIs("map")) {
@@ -1473,32 +1473,37 @@ public class XPathParserImpl
           
           if (!(tokenIs('}') && (getTokenRelative(1) == null))) {
         	  while (m_token != null) {        	 
- 	        	 String mapKeyExprStr = m_token;
+ 	        	 String mapEntryKeyXPathExprStr = m_token;
  	        	 nextToken();
  	        	 consumeExpected(':');
- 	        	 String mapValueExprStr = null;
- 	        	 if (tokenIs('[')) {
- 	        		// Possibly there is an XPath square array constructor here, 
+ 	        	 String mapEntryValueXPathExprStr = null;
+ 	        	 if (tokenIs("map")) {
+ 	        		// There's likely an XPath map constructor here, 
  	        		// within this map. 	        		
- 	        		mapValueExprStr = getSquareArrayConstructorStrValue();
+ 	        		mapEntryValueXPathExprStr = getMapConstructorStrValue();
  	        	 }
- 	        	 else if (tokenIs("map")) {
- 	        		// Possibly there is an XPath map constructor here, 
- 	        		// within this map. 	        		
- 	        		mapValueExprStr = getMapConstructorStrValue();
+ 	        	 else if (tokenIs('[')) {
+ 	        		// There's likely an XPath square array constructor here, 
+  	        		// within this map. 	        		
+  	        		mapEntryValueXPathExprStr = getXPathArrayConstructorStrValue(true); 
+ 	        	 }
+ 	        	 else if (tokenIs("array") && lookahead('{', 1)) {
+ 	        		// There's likely an XPath curly array constructor here, 
+  	        		// within this map. 	        		
+  	        		mapEntryValueXPathExprStr = getXPathArrayConstructorStrValue(false); 
  	        	 }
  	        	 else {
- 	        		// The map's key value here is a simple value (i.e, not an array or 
+ 	        		// The map's key value here is a simple value (i.e, not an array, or 
  	        		// map).
- 	        		mapValueExprStr = m_token;
+ 	        		mapEntryValueXPathExprStr = m_token;
  	        		while (!(tokenIs(',') || tokenIs('}'))) {
  	        		   nextToken();
  	        		   if (!(tokenIs(',') || tokenIs('}'))) {
- 	        		      mapValueExprStr += m_token;
+ 	        		      mapEntryValueXPathExprStr += m_token;
  	        		   }
  	        		}
  	        	 }
- 	        	 nativeMapVar.put(mapKeyExprStr, mapValueExprStr);
+ 	        	 nativeMapVar.put(mapEntryKeyXPathExprStr, mapEntryValueXPathExprStr);
  	        	 if (tokenIs(',')) {
  	        		consumeExpected(','); 
  	        	 }
@@ -1598,17 +1603,24 @@ public class XPathParserImpl
   }
   
   /**
-   *  Get XPath square array constructor's string value.
+   *  Get XPath array constructor's string value.
    */
-  private String getSquareArrayConstructorStrValue() throws TransformerException {
+  private String getXPathArrayConstructorStrValue(boolean isSquareArrayCons) throws TransformerException {
 	 StringBuffer arrayStrBuf = new StringBuffer();
 	 
 	 arrayStrBuf.append(m_token);
 	 
-	 consumeExpected('[');
+	 if (isSquareArrayCons) {
+	    consumeExpected('[');
+	 }
+	 else {		 
+		consumeExpected("array");
+		arrayStrBuf.append(m_token);
+		consumeExpected('{');
+	 }
 	 
 	 while (m_token != null) {
-		if (tokenIs(']')) {
+		if (tokenIs(']') || tokenIs('}')) {
 		   arrayStrBuf.append(m_token);
 		   break;
 		}
@@ -3271,19 +3283,19 @@ public class XPathParserImpl
 
          nextToken();
       }
-      else if (tokenIs(FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI)) 
+      else if (tokenIs(FunctionTable.XPATH_BUILT_IN_MAP_FUNCS_NS_URI)) 
       {
          nextToken();
          consumeExpected(':');
          
-         int funcTok = getFunctionToken(m_token, FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI);
+         int funcTok = getFunctionToken(m_token, FunctionTable.XPATH_BUILT_IN_MAP_FUNCS_NS_URI);
 
          if (-1 == funcTok)
          {
-           error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
-                 new Object[] {"{" + FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI + "}" + m_token + "()"});
+             error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
+                 new Object[] {"{" + FunctionTable.XPATH_BUILT_IN_MAP_FUNCS_NS_URI + "}" + m_token + "()"});
          }
-         else if (!(FunctionTable.XPATH_ARRAY_FUNC_IDS_ARR).contains(Integer.valueOf(funcTok))) {
+         else if (!(FunctionTable.XPATH_MAP_FUNC_IDS_ARR).contains(Integer.valueOf(funcTok))) {
              funcTok = -1;
              error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
                    new Object[] {"{" + FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI + "}" + m_token + "()"});  
@@ -3305,19 +3317,19 @@ public class XPathParserImpl
 
          nextToken();
       }
-      else if (tokenIs(FunctionTable.XPATH_BUILT_IN_MAP_FUNCS_NS_URI)) 
+      else if (tokenIs(FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI)) 
       {
          nextToken();
          consumeExpected(':');
          
-         int funcTok = getFunctionToken(m_token, FunctionTable.XPATH_BUILT_IN_MAP_FUNCS_NS_URI);
+         int funcTok = getFunctionToken(m_token, FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI);
 
          if (-1 == funcTok)
          {
-             error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
-                 new Object[] {"{" + FunctionTable.XPATH_BUILT_IN_MAP_FUNCS_NS_URI + "}" + m_token + "()"});
+           error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
+                 new Object[] {"{" + FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI + "}" + m_token + "()"});
          }
-         else if (!(FunctionTable.XPATH_MAP_FUNC_IDS_ARR).contains(Integer.valueOf(funcTok))) {
+         else if (!(FunctionTable.XPATH_ARRAY_FUNC_IDS_ARR).contains(Integer.valueOf(funcTok))) {
              funcTok = -1;
              error(XPATHErrorResources.ER_COULDNOT_FIND_FUNCTION,
                    new Object[] {"{" + FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI + "}" + m_token + "()"});  
diff --git a/src/org/apache/xpath/functions/map/FuncMapMerge.java b/src/org/apache/xpath/functions/map/FuncMapMerge.java
new file mode 100644
index 00000000..6ccb5550
--- /dev/null
+++ b/src/org/apache/xpath/functions/map/FuncMapMerge.java
@@ -0,0 +1,345 @@
+/*
+ * 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.functions.map;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.transform.SourceLocator;
+import javax.xml.transform.TransformerException;
+
+import org.apache.xalan.xslt.util.XslTransformEvaluationHelper;
+import org.apache.xpath.Expression;
+import org.apache.xpath.XPathContext;
+import org.apache.xpath.functions.FunctionMultiArgs;
+import org.apache.xpath.objects.ResultSequence;
+import org.apache.xpath.objects.XObject;
+import org.apache.xpath.objects.XPathMap;
+import org.apache.xpath.operations.Variable;
+
+import xml.xpath31.processor.types.XSString;
+
+/**
+ * Implementation of an map:merge function.
+ * 
+ * @author Mukul Gandhi <mu...@apache.org>
+ * 
+ * @xsl.usage advanced
+ */
+public class FuncMapMerge extends FunctionMultiArgs {
+
+	private static final long serialVersionUID = -3510616878635425133L;
+	
+	private static final String DUPLICATES_KEY_NAME = "duplicates";
+	
+	private static final String OPTION_REJECT ="reject";
+	
+	private static final String OPTION_USE_FIRST ="use-first";
+	
+	private static final String OPTION_USE_LAST ="use-last";
+	
+	private static final String OPTION_USE_ANY ="use-any";
+	
+	private static final String OPTION_COMBINE ="combine";
+	
+	private static final String[] OPTIONS_ARR = new String[] { OPTION_REJECT, OPTION_USE_FIRST, OPTION_USE_LAST, 
+			                                                   OPTION_USE_ANY, OPTION_COMBINE };
+
+	public XObject execute(XPathContext xctxt) throws javax.xml.transform.TransformerException
+	{
+		XObject result = null;
+	       
+	    SourceLocator srcLocator = xctxt.getSAXLocator();
+	       
+	    Expression arg0 = getArg0();
+	    Expression arg1 = getArg1();
+	    
+	    if ((arg0 == null) && (arg1 == null)) {
+	       throw new javax.xml.transform.TransformerException("FOAP0001 : The function call, map:merge requires either "
+	       		                                                             + "one argument (specifying maps to be merged) or two arguments "
+	       		                                                             + "(the 2nd argument is an options map).", srcLocator);
+	    }
+	    
+	    validateTypeOfFirstArg(arg0, xctxt);
+	    if (arg1 != null) {
+	       validateTypeOfSecondArg(arg1, xctxt);
+	    }
+	    
+	    if (OPTION_REJECT.equals(getOptionsStrVal(arg1, xctxt)) && isMapMergeReject(arg0, xctxt)) {
+	        throw new javax.xml.transform.TransformerException("FOJS0003 : Maps could not be merged, because one or more duplicate "
+	        		                                                               + "keys were found within maps to be merged, and an map merge "
+	        		                                                               + "option 'reject' was used.", srcLocator);
+	    }
+	    else {
+	    	// map:merge, function call was invoked with an option "reject", but the maps to be 
+	    	// merged didn't had duplicate keys (i.e, maps merge is possible).
+	    	
+	    	// For this case, we merge the maps with default value of options, i.e "use-first"
+	    	ResultSequence rSeq = null;
+	    	Map<XObject, XObject> nativeResultMap = new HashMap<XObject, XObject>();
+	    	if (arg0 instanceof Variable) {
+	    		rSeq = (ResultSequence)(((Variable)arg0).execute(xctxt));
+	    		for (int idx = rSeq.size() - 1; idx >= 0; idx--) {
+	    			XPathMap map = (XPathMap)rSeq.item(idx);
+	    			Map<XObject, XObject> nativeMap = map.getNativeMap();
+	    			nativeResultMap.putAll(nativeMap);	        	 
+	    		}
+	    		XPathMap resultMap = new XPathMap();
+	    		resultMap.setNativeMap(nativeResultMap);
+	    		result = resultMap;
+	    	}
+	    	else {
+	    		rSeq = (ResultSequence)(arg0.execute(xctxt));
+	    		for (int idx = rSeq.size() - 1; idx >= 0; idx--) {
+	    			XPathMap map = (XPathMap)rSeq.item(idx);
+	    			Map<XObject, XObject> nativeMap = map.getNativeMap();
+	    			nativeResultMap.putAll(nativeMap);	        	 
+	    		}
+	    		XPathMap resultMap = new XPathMap();
+	    		resultMap.setNativeMap(nativeResultMap);
+	    		result = resultMap;
+	    	}
+	    }
+	    
+	    if (arg1 == null || OPTION_USE_FIRST.equals(getOptionsStrVal(arg1, xctxt)) || 
+	    		            OPTION_USE_ANY.equals(getOptionsStrVal(arg1, xctxt))) {
+	    	ResultSequence rSeq = null;
+	    	Map<XObject, XObject> nativeResultMap = new HashMap<XObject, XObject>();
+	    	if (arg0 instanceof Variable) {
+	    		rSeq = (ResultSequence)(((Variable)arg0).execute(xctxt));
+	    		for (int idx = rSeq.size() - 1; idx >= 0; idx--) {
+	    			XPathMap map = (XPathMap)rSeq.item(idx);
+	    			Map<XObject, XObject> nativeMap = map.getNativeMap();
+	    			nativeResultMap.putAll(nativeMap);	        	 
+	    		}
+	    		XPathMap resultMap = new XPathMap();
+	    		resultMap.setNativeMap(nativeResultMap);
+	    		result = resultMap;
+	    	}
+	    	else {
+	    		rSeq = (ResultSequence)(arg0.execute(xctxt));
+	    		for (int idx = rSeq.size() - 1; idx >= 0; idx--) {
+	    			XPathMap map = (XPathMap)rSeq.item(idx);
+	    			Map<XObject, XObject> nativeMap = map.getNativeMap();
+	    			nativeResultMap.putAll(nativeMap);	        	 
+	    		}
+	    		XPathMap resultMap = new XPathMap();
+	    		resultMap.setNativeMap(nativeResultMap);
+	    		result = resultMap;
+	    	}
+	    }
+	    else if (OPTION_USE_LAST.equals(getOptionsStrVal(arg1, xctxt))) {
+	    	ResultSequence rSeq = null;
+	    	Map<XObject, XObject> nativeResultMap = new HashMap<XObject, XObject>();
+	    	if (arg0 instanceof Variable) {
+	    		rSeq = (ResultSequence)(((Variable)arg0).execute(xctxt));
+	    		for (int idx = 0; idx < rSeq.size(); idx++) {
+	    			XPathMap map = (XPathMap)rSeq.item(idx);
+	    			Map<XObject, XObject> nativeMap = map.getNativeMap();
+	    			nativeResultMap.putAll(nativeMap);	        	 
+	    		}
+	    		XPathMap resultMap = new XPathMap();
+	    		resultMap.setNativeMap(nativeResultMap);
+	    		result = resultMap;
+	    	}
+	    	else {
+	    		rSeq = (ResultSequence)(arg0.execute(xctxt));
+	    		for (int idx = 0; idx < rSeq.size(); idx++) {
+	    			XPathMap map = (XPathMap)rSeq.item(idx);
+	    			Map<XObject, XObject> nativeMap = map.getNativeMap();
+	    			nativeResultMap.putAll(nativeMap);	        	 
+	    		}
+	    		XPathMap resultMap = new XPathMap();
+	    		resultMap.setNativeMap(nativeResultMap);
+	    		result = resultMap;
+	    	}
+	    }
+	    
+	    return result;
+	}
+	
+	/**
+	 * Should the merge of maps be rejected, due to any duplicate keys present within maps.
+	 */
+	private boolean isMapMergeReject(Expression arg0, XPathContext xctxt) throws TransformerException {
+		
+		boolean isMapMergeReject = true;
+		
+		ResultSequence rSeq = null;
+		
+		if (arg0 instanceof Variable) {
+    	   rSeq = (ResultSequence)(((Variable)arg0).execute(xctxt));
+    	}
+		else {
+		   rSeq = (ResultSequence)(arg0.execute(xctxt));	
+		}
+		
+		int totalKeys = 0;
+		
+		Set<XObject> mergeOfKeysSet = new HashSet<XObject>();
+		
+		for (int idx = 0; idx < rSeq.size(); idx++) {
+		   XPathMap map = (XPathMap)rSeq.item(idx);
+		   Map<XObject, XObject> nativeMap = map.getNativeMap();
+		   Set<XObject> keysSet = nativeMap.keySet();
+		   totalKeys += keysSet.size();
+		   mergeOfKeysSet.addAll(keysSet);
+		}
+		
+		int countOfKeysAfterKeyMerge = mergeOfKeysSet.size();
+		
+		if (totalKeys == countOfKeysAfterKeyMerge) {
+			isMapMergeReject = false;
+		}
+		
+		return isMapMergeReject;
+	}
+
+	/**
+	 * Get the string value, of map's key named "duplicates".
+	 */
+	private String getOptionsStrVal(Expression expr, XPathContext xctxt) throws TransformerException {
+		
+	   String optionsStrVal = null;
+	   
+	   if (expr instanceof Variable) {
+	      XObject obj = ((Variable)expr).execute(xctxt);
+	      XPathMap xpathMap = (XPathMap)obj;
+	      obj = xpathMap.get(new XSString(DUPLICATES_KEY_NAME));
+	      optionsStrVal = XslTransformEvaluationHelper.getStrVal(obj);  
+	   }
+	   else {
+		  XObject obj = expr.execute(xctxt);
+		  XPathMap xpathMap = (XPathMap)obj;
+	      obj = xpathMap.get(new XSString(DUPLICATES_KEY_NAME));
+	      optionsStrVal = XslTransformEvaluationHelper.getStrVal(obj);
+	   }
+	   
+	   return optionsStrVal; 
+	}
+
+	/**
+     * Validate the expected type of map:merge function's, 1st argument.
+	 */
+	private void validateTypeOfFirstArg(Expression arg, XPathContext xctxt) throws TransformerException {
+		
+		SourceLocator srcLocator = xctxt.getSAXLocator();
+		
+		if (arg instanceof Variable) {
+	       XObject obj = ((Variable)arg).execute(xctxt);
+	       if (obj instanceof ResultSequence) {
+	    	  ResultSequence rSeq = (ResultSequence)obj;
+	    	  for (int idx = 0; idx < rSeq.size(); idx++) {
+	    		 XObject obj1 = rSeq.item(idx);
+	    		 if (!(obj1 instanceof XPathMap)) {
+	    			 throw new TransformerException("FOAP0001 : Within map:merge function's 1st argument sequence, "
+	    			 		                                         + "item at position " + (idx + 1) + " is not a map.", 
+	    			 		                                               srcLocator); 
+	    		 }
+	    	  }
+	       }
+	       else {
+	    	  throw new TransformerException("FOAP0001 : The map:merge function's 1st argument should be a "
+	    	  		                                       + "sequence of one or more maps.", srcLocator);   
+	       }
+		}
+		else {
+		   XObject obj = arg.execute(xctxt);
+           if (obj instanceof ResultSequence) {
+        	  ResultSequence rSeq = (ResultSequence)obj;
+ 	    	  for (int idx = 0; idx < rSeq.size(); idx++) {
+ 	    		 XObject obj1 = rSeq.item(idx);
+ 	    		 if (!(obj1 instanceof XPathMap)) {
+ 	    			 throw new TransformerException("FOAP0001 : Within map:merge function's 1st argument sequence, "
+ 	    			 		                                         + "item at position " + (idx + 1) + " is not a map.", 
+ 	    			 		                                               srcLocator); 
+ 	    		 }
+ 	    	  } 
+	       }
+	       else {
+	    	  throw new TransformerException("FOAP0001 : The map:merge function's 1st argument should be a "
+	    	  		                                       + "sequence of one or more maps.", srcLocator);   
+	       }
+		}
+	}
+	
+	/**
+     * Validate the expected type of map:merge function's, 2nd argument.
+	 */
+	private void validateTypeOfSecondArg(Expression arg, XPathContext xctxt) throws TransformerException {        
+        
+		SourceLocator srcLocator = xctxt.getSAXLocator();
+        
+        if (arg instanceof Variable) {
+ 	       XObject obj = ((Variable)arg).execute(xctxt);
+ 	       if (obj instanceof XPathMap) {
+ 	    	  validateOptionsMap(obj, srcLocator);
+ 	       }
+ 		}
+ 		else {
+ 		   XObject obj = arg.execute(xctxt);
+ 		   if (obj instanceof XPathMap) {
+ 			  validateOptionsMap(obj, srcLocator);
+ 	       }
+ 		}
+		
+	}
+
+	/**
+	 * This method does, validation of map:merge function's options map, and is 
+	 * used by the method 'validateTypeOfSecondArg'. 
+	 */
+	private void validateOptionsMap(XObject obj, SourceLocator srcLocator) throws TransformerException {
+		
+		XPathMap optionsMap = (XPathMap)obj;
+		
+		if (optionsMap.size() != 1) {
+			throw new TransformerException("FOAP0001 : The map:merge function's 2nd argument if present, should be "
+					                                     + "a map having only 1 entry with key named '" + DUPLICATES_KEY_NAME + 
+					                                       "'.", srcLocator);  
+		}
+		else {
+			XObject mapEntryValue = optionsMap.get(new XSString(DUPLICATES_KEY_NAME));
+			if (mapEntryValue == null) {
+				throw new TransformerException("FOAP0001 : The map:merge function's 2nd argument if present, should be "
+						                                     + "a map having only 1 entry with key named '" + DUPLICATES_KEY_NAME + 
+						                                       "'.", srcLocator); 
+			}
+			else {
+				boolean isOptionsValueOk = false;
+				String mapEntryStrVal = XslTransformEvaluationHelper.getStrVal(mapEntryValue);
+				for (int idx = 0; idx < OPTIONS_ARR.length; idx++) {
+					String allowedVal = OPTIONS_ARR[idx];
+					if (allowedVal.equals(mapEntryStrVal)) {
+						isOptionsValueOk = true;
+						break;
+					}
+				}
+
+				if (!isOptionsValueOk) {
+					throw new TransformerException("FOJS0005 : The allowed values of the map:merge duplicate resolution "
+							                                     + "options are : ['reject', 'use-first', 'use-last', 'use-any', "
+							                                     + "'combine']. An invalid options value '" + mapEntryStrVal + 
+							                                     "' was provided.", srcLocator);
+				}
+			}
+		}
+	}
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@xalan.apache.org
For additional commands, e-mail: commits-help@xalan.apache.org