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/05/30 12:39:11 UTC
(xalan-java) branch xalan-j_xslt3.0 updated: committing implementation of xpath3.1 function map:for-each, and few related working test cases
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 c7cba932 committing implementation of xpath3.1 function map:for-each, and few related working test cases
c7cba932 is described below
commit c7cba932240223105dbff260b6ee2b38342eafac
Author: Mukul Gandhi <ga...@gmail.com>
AuthorDate: Thu May 30 18:08:57 2024 +0530
committing implementation of xpath3.1 function map:for-each, and few related working test cases
---
src/org/apache/xpath/compiler/FunctionTable.java | 11 +-
src/org/apache/xpath/compiler/Keywords.java | 3 +
src/org/apache/xpath/compiler/XPathParserImpl.java | 3 +
.../apache/xpath/functions/map/FuncMapForEach.java | 222 +++++++++++++++++++++
tests/org/apache/xalan/xpath3/XPathMapTests.java | 20 ++
tests/xpath_maps/gold/test15.out | 5 +
tests/xpath_maps/gold/test16.out | 5 +
tests/xpath_maps/test15.xsl | 76 +++++++
tests/xpath_maps/test16.xsl | 76 +++++++
9 files changed, 419 insertions(+), 2 deletions(-)
diff --git a/src/org/apache/xpath/compiler/FunctionTable.java b/src/org/apache/xpath/compiler/FunctionTable.java
index 0433ea4e..0161cb64 100644
--- a/src/org/apache/xpath/compiler/FunctionTable.java
+++ b/src/org/apache/xpath/compiler/FunctionTable.java
@@ -468,6 +468,9 @@ public class FunctionTable
/** The 'array:reverse()' id. */
public static final int FUNC_ARRAY_REVERSE = 150;
+
+ /** The 'map:for-each()' id. */
+ public static final int FUNC_MAP_FOREACH = 151;
// Proprietary
@@ -529,7 +532,8 @@ public class FunctionTable
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_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);
@@ -555,7 +559,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 = 151;
+ private static final int NUM_BUILT_IN_FUNCS = 152;
/**
* Number of built-in functions that may be added.
@@ -810,6 +814,7 @@ public class FunctionTable
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;
@@ -1086,6 +1091,8 @@ public class FunctionTable
new Integer(FunctionTable.FUNC_MAP_PUT));
m_functionID.put(Keywords.FUNC_MAP_ENTRY,
new Integer(FunctionTable.FUNC_MAP_ENTRY));
+ m_functionID.put(Keywords.FUNC_MAP_FOREACH,
+ new Integer(FunctionTable.FUNC_MAP_FOREACH));
// XPath 3.1 functions configurations for the array functions
// namespace http://www.w3.org/2005/xpath-functions/array
diff --git a/src/org/apache/xpath/compiler/Keywords.java b/src/org/apache/xpath/compiler/Keywords.java
index 70b38dc6..1eb9dca2 100644
--- a/src/org/apache/xpath/compiler/Keywords.java
+++ b/src/org/apache/xpath/compiler/Keywords.java
@@ -584,6 +584,9 @@ public class Keywords
/** map:entry function string. */
public static final String FUNC_MAP_ENTRY = "entry";
+ /** map:for-each function string. */
+ public static final String FUNC_MAP_FOREACH = "for-each";
+
/** parse-json function string. */
public static final String FUNC_PARSE_JSON = "parse-json";
diff --git a/src/org/apache/xpath/compiler/XPathParserImpl.java b/src/org/apache/xpath/compiler/XPathParserImpl.java
index 64575722..427f9b93 100644
--- a/src/org/apache/xpath/compiler/XPathParserImpl.java
+++ b/src/org/apache/xpath/compiler/XPathParserImpl.java
@@ -1075,6 +1075,9 @@ public class XPathParserImpl
else if ("entry".equals(key)) {
id = FunctionTable.FUNC_MAP_ENTRY;
}
+ else if ("for-each".equals(key)) {
+ id = FunctionTable.FUNC_MAP_FOREACH;
+ }
}
else if ((FunctionTable.XPATH_BUILT_IN_ARRAY_FUNCS_NS_URI).equals(nsUri)) {
if ("size".equals(key)) {
diff --git a/src/org/apache/xpath/functions/map/FuncMapForEach.java b/src/org/apache/xpath/functions/map/FuncMapForEach.java
new file mode 100644
index 00000000..f5121dc1
--- /dev/null
+++ b/src/org/apache/xpath/functions/map/FuncMapForEach.java
@@ -0,0 +1,222 @@
+/*
+ * 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.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.xml.transform.SourceLocator;
+import javax.xml.transform.TransformerException;
+
+import org.apache.xalan.res.XSLMessages;
+import org.apache.xml.dtm.DTM;
+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.functions.Function2Args;
+import org.apache.xpath.functions.WrongNumberArgsException;
+import org.apache.xpath.objects.InlineFunction;
+import org.apache.xpath.objects.InlineFunctionParameter;
+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 org.apache.xpath.res.XPATHErrorResources;
+
+/**
+ * Implementation of map:for-each function.
+ *
+ * @author Mukul Gandhi <mu...@apache.org>
+ *
+ * @xsl.usage advanced
+ */
+public class FuncMapForEach extends Function2Args {
+
+ private static final long serialVersionUID = 4474670594915534994L;
+
+ // 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;
+
+ /**
+ * Implementation of the function. The function must return a valid object.
+ *
+ * @param xctxt The current execution context.
+ *
+ * @return A valid XObject.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ public XObject execute(XPathContext xctxt) throws javax.xml.transform.TransformerException {
+
+ XObject result = null;
+
+ SourceLocator srcLocator = xctxt.getSAXLocator();
+
+ Expression arg0 = getArg0();
+ Expression arg1 = getArg1();
+
+ XObject arg0XsObject = null;
+
+ if (fVars != null) {
+ arg0.fixupVariables(fVars, fGlobalsSize);
+ }
+
+ if (arg0 instanceof XPathMap) {
+ arg0XsObject = (XPathMap)arg0;
+ }
+ else if (arg0 instanceof Variable) {
+ arg0XsObject = ((Variable)arg0).execute(xctxt);
+ if (!(arg0XsObject instanceof XPathMap)) {
+ throw new javax.xml.transform.TransformerException("FORG0006 : The 1st argument to function call map:for-each, "
+ + "is not a map.", xctxt.getSAXLocator());
+ }
+ }
+ else {
+ throw new javax.xml.transform.TransformerException("FORG0006 : The 1st argument to function call map:for-each, "
+ + "is not a map.", xctxt.getSAXLocator());
+ }
+
+ if (arg1 instanceof InlineFunction) {
+ InlineFunction inlineFuncArg = (InlineFunction)arg1;
+ verifyInlineFunctionParamCardinality(inlineFuncArg, srcLocator);
+ result = evaluateFnMapforEach(xctxt, (XPathMap)arg0XsObject, inlineFuncArg);
+ }
+ else if (arg1 instanceof Variable) {
+ if (fVars != null) {
+ arg1.fixupVariables(fVars, fGlobalsSize);
+ }
+
+ XObject arg1VarValue = arg1.execute(xctxt);
+ if (arg1VarValue instanceof InlineFunction) {
+ InlineFunction inlineFuncArg = (InlineFunction)arg1VarValue;
+ verifyInlineFunctionParamCardinality(inlineFuncArg, srcLocator);
+ result = evaluateFnMapforEach(xctxt, (XPathMap)arg0XsObject, inlineFuncArg);
+ }
+ else {
+ throw new javax.xml.transform.TransformerException("FORG0006 : The 2nd argument to function call map:for-each, "
+ + "is not a function item.", xctxt.getSAXLocator());
+ }
+ }
+ else {
+ throw new javax.xml.transform.TransformerException("FORG0006 : The 2nd argument to function call map:for-each, "
+ + "is not a function item.", xctxt.getSAXLocator());
+ }
+
+ return result;
+ }
+
+ /**
+ * Check that the number of arguments passed to this function is correct.
+ *
+ * @param argNum The number of arguments that is being passed to the function.
+ *
+ * @throws WrongNumberArgsException
+ */
+ public void checkNumberArgs(int argNum) throws WrongNumberArgsException
+ {
+ if (argNum != 2) {
+ reportWrongNumberArgs();
+ }
+ }
+
+ /**
+ * Constructs and throws a WrongNumberArgException with the appropriate
+ * message for this function object.
+ *
+ * @throws WrongNumberArgsException
+ */
+ protected void reportWrongNumberArgs() throws WrongNumberArgsException {
+ throw new WrongNumberArgsException(XSLMessages.createXPATHMessage(
+ XPATHErrorResources.ER_TWO, null)); //"2"
+ }
+
+ public void fixupVariables(java.util.Vector vars, int globalsSize)
+ {
+ fVars = (Vector)(vars.clone());
+ fGlobalsSize = globalsSize;
+ }
+
+ /*
+ * Verify the number of function parameters, that the inline function is allowed to
+ * have for map:for-each function call.
+ */
+ private void verifyInlineFunctionParamCardinality(InlineFunction inlineFuncArg, SourceLocator srcLocator) throws
+ javax.xml.transform.TransformerException {
+ List<InlineFunctionParameter> funcParamList = inlineFuncArg.getFuncParamList();
+ if (funcParamList.size() != 2) {
+ throw new javax.xml.transform.TransformerException("XPTY0004 : The supplied function map:for-each's function item has " +
+ funcParamList.size() + " parameters. Expected 2.", srcLocator);
+ }
+ }
+
+ /*
+ * Construct result of map:for-each function call.
+ */
+ private ResultSequence evaluateFnMapforEach(XPathContext xctxt, XPathMap xpathMap, InlineFunction funcItem)
+ throws TransformerException {
+ ResultSequence resultSeq = new ResultSequence();
+
+ List<InlineFunctionParameter> funcParamList = funcItem.getFuncParamList();
+ QName fiParam0 = new QName((funcParamList.get(0)).getParamName());
+ QName fiParam1 = new QName((funcParamList.get(1)).getParamName());
+
+ String funcBodyXPathExprStr = funcItem.getFuncBodyXPathExprStr();
+
+ if (funcBodyXPathExprStr == null || "".equals(funcBodyXPathExprStr)) {
+ return resultSeq;
+ }
+
+ SourceLocator srcLocator = xctxt.getSAXLocator();
+ XPath inlineFnXpath = new XPath(funcBodyXPathExprStr, srcLocator, null, XPath.SELECT, null);
+
+ Map<XObject, XObject> nativeMap = xpathMap.getNativeMap();
+ Set<Entry<XObject, XObject>> mapEntrySet = nativeMap.entrySet();
+
+ Iterator<Entry<XObject, XObject>> iter = mapEntrySet.iterator();
+
+ XPathContext xpathContextNew = new XPathContext(false);
+ Map<QName, XObject> inlineFunctionVarMap = xpathContextNew.getXPathVarMap();
+
+ while (iter.hasNext()) {
+ Entry<XObject, XObject> mapEntry = iter.next();
+ XObject key = mapEntry.getKey();
+ XObject value = mapEntry.getValue();
+
+ if (fiParam0 != null) {
+ inlineFunctionVarMap.put(fiParam0, key);
+ }
+ if (fiParam1 != null) {
+ inlineFunctionVarMap.put(fiParam1, value);
+ }
+
+ XObject resultObj = inlineFnXpath.execute(xpathContextNew, DTM.NULL, null);
+ resultSeq.add(resultObj);
+ }
+
+ inlineFunctionVarMap.clear();
+
+ return resultSeq;
+ }
+}
diff --git a/tests/org/apache/xalan/xpath3/XPathMapTests.java b/tests/org/apache/xalan/xpath3/XPathMapTests.java
index f6d1a39b..b0aae9a3 100644
--- a/tests/org/apache/xalan/xpath3/XPathMapTests.java
+++ b/tests/org/apache/xalan/xpath3/XPathMapTests.java
@@ -187,5 +187,25 @@ public class XPathMapTests extends XSLTransformTestsUtil {
runXslTransformAndAssertOutput(xmlFilePath, xslFilePath, goldFilePath, null);
}
+
+ @Test
+ public void xslMapTest15() {
+ String xmlFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test15.xsl";
+ String xslFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test15.xsl";
+
+ String goldFilePath = XSL_TRANSFORM_GOLD_DIRPATH + "test15.out";
+
+ runXslTransformAndAssertOutput(xmlFilePath, xslFilePath, goldFilePath, null);
+ }
+
+ @Test
+ public void xslMapTest16() {
+ String xmlFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test16.xsl";
+ String xslFilePath = XSL_TRANSFORM_INPUT_DIRPATH + "test16.xsl";
+
+ String goldFilePath = XSL_TRANSFORM_GOLD_DIRPATH + "test16.out";
+
+ runXslTransformAndAssertOutput(xmlFilePath, xslFilePath, goldFilePath, null);
+ }
}
diff --git a/tests/xpath_maps/gold/test15.out b/tests/xpath_maps/gold/test15.out
new file mode 100644
index 00000000..b23e43d3
--- /dev/null
+++ b/tests/xpath_maps/gold/test15.out
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?><result>
+ <one itemCount="2">
+ <ok>true ### true</ok>
+ </one>
+</result>
diff --git a/tests/xpath_maps/gold/test16.out b/tests/xpath_maps/gold/test16.out
new file mode 100644
index 00000000..b23e43d3
--- /dev/null
+++ b/tests/xpath_maps/gold/test16.out
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?><result>
+ <one itemCount="2">
+ <ok>true ### true</ok>
+ </one>
+</result>
diff --git a/tests/xpath_maps/test15.xsl b/tests/xpath_maps/test15.xsl
new file mode 100644
index 00000000..21fed8b0
--- /dev/null
+++ b/tests/xpath_maps/test15.xsl
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:map="http://www.w3.org/2005/xpath-functions/map"
+ exclude-result-prefixes="xs map"
+ version="3.0">
+
+ <!-- Author: mukulg@apache.org -->
+
+ <xsl:output method="xml" indent="yes"/>
+
+ <!-- An XSLT test case, to test XPath 3.1 function map:for-each. -->
+
+ <xsl:template match="/">
+ <result>
+ <xsl:variable name="map1" select="map{1 : 'yes', 2 : 'no'}"/>
+ <xsl:variable name="action1" select="function($k, $v) { $k }"/>
+ <xsl:variable name="seq1" select="map:for-each($map1, $action1)"/>
+ <one itemCount="{count($seq1)}">
+ <ok>
+ <xsl:call-template name="exists">
+ <xsl:with-param name="seq1" select="$seq1"/>
+ <xsl:with-param name="value1" select="1"/>
+ </xsl:call-template>
+ <xsl:text> ### </xsl:text>
+ <xsl:call-template name="exists">
+ <xsl:with-param name="seq1" select="$seq1"/>
+ <xsl:with-param name="value1" select="2"/>
+ </xsl:call-template>
+ </ok>
+ </one>
+ </result>
+ </xsl:template>
+
+ <!-- An XSL named template, to check whether a sequence
+ contains a specific value. -->
+ <xsl:template name="exists" as="xs:boolean">
+ <xsl:param name="seq1" as="xs:integer*"/>
+ <xsl:param name="value1" as="xs:integer"/>
+ <xsl:variable name="temp1">
+ <xsl:for-each select="$seq1">
+ <xsl:variable name="x1" select="."/>
+ <xsl:if test="$x1 eq $value1">
+ <yes/>
+ </xsl:if>
+ </xsl:for-each>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$temp1/yes">
+ <xsl:value-of select="xs:boolean(1)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="xs:boolean(0)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </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>
diff --git a/tests/xpath_maps/test16.xsl b/tests/xpath_maps/test16.xsl
new file mode 100644
index 00000000..6d23b024
--- /dev/null
+++ b/tests/xpath_maps/test16.xsl
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:map="http://www.w3.org/2005/xpath-functions/map"
+ exclude-result-prefixes="xs map"
+ version="3.0">
+
+ <!-- Author: mukulg@apache.org -->
+
+ <xsl:output method="xml" indent="yes"/>
+
+ <!-- An XSLT test case, to test XPath 3.1 function map:for-each. -->
+
+ <xsl:template match="/">
+ <result>
+ <xsl:variable name="map1" select="map{1 : 'yes', 2 : 'no'}"/>
+ <xsl:variable name="action1" select="function($k, $v) { $v }"/>
+ <xsl:variable name="seq1" select="distinct-values(map:for-each($map1, $action1))"/>
+ <one itemCount="{count($seq1)}">
+ <ok>
+ <xsl:call-template name="exists">
+ <xsl:with-param name="seq1" select="$seq1"/>
+ <xsl:with-param name="value1" select="'yes'"/>
+ </xsl:call-template>
+ <xsl:text> ### </xsl:text>
+ <xsl:call-template name="exists">
+ <xsl:with-param name="seq1" select="$seq1"/>
+ <xsl:with-param name="value1" select="'no'"/>
+ </xsl:call-template>
+ </ok>
+ </one>
+ </result>
+ </xsl:template>
+
+ <!-- An XSL named template, to check whether a sequence
+ contains a specific value. -->
+ <xsl:template name="exists" as="xs:boolean">
+ <xsl:param name="seq1" as="xs:string*"/>
+ <xsl:param name="value1" as="xs:string"/>
+ <xsl:variable name="temp1">
+ <xsl:for-each select="$seq1">
+ <xsl:variable name="x1" select="."/>
+ <xsl:if test="$x1 eq $value1">
+ <yes/>
+ </xsl:if>
+ </xsl:for-each>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="$temp1/yes">
+ <xsl:value-of select="xs:boolean(1)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="xs:boolean(0)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </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>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@xalan.apache.org
For additional commands, e-mail: commits-help@xalan.apache.org