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