You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by js...@apache.org on 2002/11/27 18:21:16 UTC
cvs commit: jakarta-commons-sandbox/jelly/src/test/org/apache/commons/jelly/xml xpathSortExample.jelly suite.jelly
jstrachan 2002/11/27 09:21:16
Modified: jelly/src/java/org/apache/commons/jelly/tags/xml SetTag.java
XMLTagLibrary.java ForEachTag.java
jelly/src/test/org/apache/commons/jelly/xml suite.jelly
Added: jelly/src/java/org/apache/commons/jelly/tags/xml
XPathComparator.java SortTag.java
jelly/src/test/org/apache/commons/jelly/xml
xpathSortExample.jelly
Log:
Applied Jason Horman's patch to support XPath sorting when navigating over XML data via <x:forEach/>. Thanks for your patch Jason!
Revision Changes Path
1.10 +47 -11 jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/SetTag.java
Index: SetTag.java
===================================================================
RCS file: /home/cvs/jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/SetTag.java,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -r1.9 -r1.10
--- SetTag.java 30 Oct 2002 19:16:23 -0000 1.9
+++ SetTag.java 27 Nov 2002 17:21:16 -0000 1.10
@@ -61,18 +61,17 @@
*/
package org.apache.commons.jelly.tags.xml;
-import java.io.Writer;
-
-import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.MissingAttributeException;
-import org.apache.commons.jelly.Script;
-import org.apache.commons.jelly.TagSupport;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jaxen.XPath;
+import org.jaxen.JaxenException;
+
+import java.util.List;
+import java.util.Collections;
/** A tag which defines a variable from an XPath expression
*
@@ -90,7 +89,11 @@
/** The XPath expression to evaluate. */
private XPath select;
+ /** Xpath comparator for sorting */
+ private XPathComparator xpCmp = null;
+
public SetTag() {
+
}
// Tag interface
@@ -105,7 +108,14 @@
Object xpathContext = getXPathContext();
Object value = select.evaluate(xpathContext);
-
+
+ if (value instanceof List) {
+ // sort the list if xpCmp is set.
+ if (xpCmp != null && (xpCmp.getXpath() != null)) {
+ Collections.sort((List)value, xpCmp);
+ }
+ }
+
//log.info( "Evaluated xpath: " + select + " as: " + value + " of type: " + value.getClass().getName() );
context.setVariable(var, value);
@@ -123,5 +133,31 @@
/** Sets the XPath expression to evaluate. */
public void setSelect(XPath select) {
this.select = select;
+ }
+
+
+ /** Sets the xpath expression to use to sort selected nodes.
+ */
+ public void setSort(XPath sortXPath) throws JaxenException {
+ if (xpCmp == null) xpCmp = new XPathComparator();
+ xpCmp.setXpath(sortXPath);
+ }
+
+ /**
+ * Set whether to sort ascending or descending.
+ */
+ public void setDescending(boolean descending) {
+ if (xpCmp == null) xpCmp = new XPathComparator();
+ xpCmp.setDescending(descending);
+ }
+
+ /**
+ * Set the data type to convert nodes being sorted on into before sorting.
+ * This should be the name of a class that commons.beanutils knows how to convert strings
+ * into.
+ */
+ public void setSortDataType(String sortType) throws ClassNotFoundException {
+ if (xpCmp == null) xpCmp = new XPathComparator();
+ xpCmp.setType(Class.forName(sortType));
}
}
1.17 +3 -2 jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/XMLTagLibrary.java
Index: XMLTagLibrary.java
===================================================================
RCS file: /home/cvs/jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/XMLTagLibrary.java,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -r1.16 -r1.17
--- XMLTagLibrary.java 27 Nov 2002 12:43:19 -0000 1.16
+++ XMLTagLibrary.java 27 Nov 2002 17:21:16 -0000 1.17
@@ -100,6 +100,7 @@
registerTag("copy", CopyTag.class);
registerTag("copyOf", CopyOfTag.class);
registerTag("doctype", DoctypeTag.class);
+ registerTag("sort", SortTag.class);
}
public Expression createExpression(
@@ -110,7 +111,7 @@
// #### may need to include some namespace URI information in the XPath instance?
- if (attributeName.equals("select")) {
+ if (attributeName.equals("select") || attributeName.equals("sort")) {
if ( log.isDebugEnabled() ) {
log.debug( "Parsing XPath expression: " + attributeValue );
}
1.11 +46 -12 jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/ForEachTag.java
Index: ForEachTag.java
===================================================================
RCS file: /home/cvs/jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/ForEachTag.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- ForEachTag.java 30 Oct 2002 19:16:23 -0000 1.10
+++ ForEachTag.java 27 Nov 2002 17:21:16 -0000 1.11
@@ -63,13 +63,12 @@
import java.util.Iterator;
import java.util.List;
+import java.util.Collections;
-import org.apache.commons.jelly.JellyContext;
-import org.apache.commons.jelly.Script;
-import org.apache.commons.jelly.TagSupport;
import org.apache.commons.jelly.XMLOutput;
import org.jaxen.XPath;
+import org.jaxen.JaxenException;
/** A tag which performs an iteration over the results of an XPath expression
*
@@ -81,14 +80,17 @@
/** Holds the XPath selector. */
private XPath select;
+ /** Xpath comparator for sorting */
+ private XPathComparator xpCmp = null;
+
/** If specified then the current item iterated through will be defined
* as the given variable name. */
private String var;
/** The current iteration value */
private Object iterationValue;
-
-
+
+
public ForEachTag() {
}
@@ -96,7 +98,14 @@
//-------------------------------------------------------------------------
public void doTag(XMLOutput output) throws Exception {
if (select != null) {
- Iterator iter = select.selectNodes( getXPathContext() ).iterator();
+ List nodes = select.selectNodes( getXPathContext() );
+
+ // sort the list if xpCmp is set.
+ if (xpCmp != null && (xpCmp.getXpath() != null)) {
+ Collections.sort(nodes, xpCmp);
+ }
+
+ Iterator iter = nodes.iterator();
while (iter.hasNext()) {
iterationValue = iter.next();
if (var != null) {
@@ -125,10 +134,35 @@
public void setSelect(XPath select) {
this.select = select;
}
+
/** Sets the variable name to export for the item being iterated over
*/
public void setVar(String var) {
this.var = var;
}
-
+
+ /** Sets the xpath expression to use to sort selected nodes.
+ */
+ public void setSort(XPath sortXPath) throws JaxenException {
+ if (xpCmp == null) xpCmp = new XPathComparator();
+ xpCmp.setXpath(sortXPath);
+ }
+
+ /**
+ * Set whether to sort ascending or descending.
+ */
+ public void setDescending(boolean descending) {
+ if (xpCmp == null) xpCmp = new XPathComparator();
+ xpCmp.setDescending(descending);
+ }
+
+ /**
+ * Set the data type to convert nodes being sorted on into before sorting. This
+ * should be the name of a class that commons.beanutils knows how to convert strings
+ * into.
+ */
+ public void setSortDataType(String sortType) throws ClassNotFoundException {
+ if (xpCmp == null) xpCmp = new XPathComparator();
+ xpCmp.setType(Class.forName(sortType));
+ }
}
1.1 jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/XPathComparator.java
Index: XPathComparator.java
===================================================================
/*
* $Header: /home/cvspublic/jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/ForEachTag.java,v 1.10 2002/10/30 19:16:23 jstrachan Exp $
* $Revision: 1.10 $
* $Date: 2002/10/30 19:16:23 $
*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* $Id$
*/
package org.apache.commons.jelly.tags.xml;
import java.util.Comparator;
import org.dom4j.Node;
import org.jaxen.XPath;
import org.jaxen.JaxenException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.jelly.util.NestedRuntimeException;
/**
* Compares xml nodes by extracting the value at xpath and
* comparing it.
*
* @author <a href="mailto:jason@jhorman.org">Jason Horman</a>
* @version $Id$
*/
public class XPathComparator implements Comparator {
/** The xpath to use to extract value from nodes to compare */
private XPath xpath = null;
/** If set then the value extracted will be cast into this type and compared. */
private Class type = null;
/** Sort descending or ascending */
private boolean descending = false;
public XPathComparator() {
}
public XPathComparator(XPath xpath, boolean descending) {
this.xpath = xpath;
this.descending = descending;
}
public XPathComparator(XPath xpath, Class type, boolean descending) {
this.xpath = xpath;
this.type = type;
this.descending = descending;
}
public void setXpath(XPath xpath) {
this.xpath = xpath;
}
public XPath getXpath() {
return xpath;
}
public void setType(Class type) {
this.type = type;
}
public void setDescending(boolean descending) {
this.descending = descending;
}
public int compare(Object o1, Object o2) {
return compare((Node)o1, (Node)o2);
}
public int compare(Node n1, Node n2) {
try {
// apply the xpaths. not using stringValueOf since I don't
// want all of the child nodes appended to the strings
Node val1 = (Node)xpath.selectSingleNode(n1);
Node val2 = (Node)xpath.selectSingleNode(n2);
// return if null
if (val1 == null || val2 == null) {
return val1 == null ? (val2 == null ? 1 : -1) : 1;
}
// these are what will be compared
Comparable c1, c2;
// extract the string values
String s1 = val1.getText();
String s2 = val2.getText();
// if type is set convert to it and compare
// if it is not set try to infer types
if (type != null) {
c1 = (Comparable)ConvertUtils.convert(s1, type);
c2 = (Comparable)ConvertUtils.convert(s2, type);
} else {
// check if numeric type
if (isNumeric(s1) && isNumeric(s2)) {
// if either is a double, cast to doubles
if (isDouble(s1) || isDouble(s2)) {
c1 = (Double)ConvertUtils.convert(s1, Double.class);
c2 = (Double)ConvertUtils.convert(s2, Double.class);
} else {
c1 = (Integer)ConvertUtils.convert(s1, Integer.class);
c2 = (Integer)ConvertUtils.convert(s2, Integer.class);
}
} else {
// nope, leave as strings
c1 = s1;
c2 = s2;
}
}
// compare descending or ascending
if (!descending) {
return c1.compareTo(c2);
} else {
return c2.compareTo(c1);
}
} catch (JaxenException e) {
throw new XPathSortException("error sorting nodes", e);
}
}
/**
* Check to see if a string is a number. Negative and decimals supported.
* @param str String to check
* @return True if the string is numeric. Empty strings are not numeric.
*/
private static final boolean isNumeric(String str) {
final int strLen = str.length();
// empty strings are not numbers
if (strLen == 0) return false;
// start at pos 1 if the 1st char is '-' to support negatives
final int startPos = (str.charAt(0) == '-') ? 1 : 0;
if (startPos == strLen) return false;
for (int i=startPos;i<strLen;i++) {
char ch = str.charAt(i);
if ((ch < '0' || ch > '9') && (ch != '.')) {
return false;
}
}
return true;
}
/**
* Check to see if the number has a period.
*/
private static final boolean isDouble(String str) {
return str.indexOf(".") != -1;
}
/**
* My own runtime exception in case something goes wrong with sort.
*/
public static class XPathSortException extends NestedRuntimeException {
public XPathSortException(String message, Throwable cause) {
super(message, cause);
}
}
}
1.1 jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/SortTag.java
Index: SortTag.java
===================================================================
/*
* $Header: /home/cvspublic/jakarta-commons-sandbox/jelly/src/java/org/apache/commons/jelly/tags/xml/ForEachTag.java,v 1.10 2002/10/30 19:16:23 jstrachan Exp $
* $Revision: 1.10 $
* $Date: 2002/10/30 19:16:23 $
*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* $Id: ForEachTag.java,v 1.10 2002/10/30 19:16:23 jstrachan Exp $
*/
package org.apache.commons.jelly.tags.xml;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jelly.MissingAttributeException;
import org.jaxen.XPath;
import org.jaxen.JaxenException;
import java.util.List;
import java.util.Collections;
/** A tag that can sort a list of xml nodes via an xpath expression.
*
* @author <a href="mailto:jason@jhorman.org">Jason Horman</a>
* @version $Id$
*/
public class SortTag extends XPathTagSupport {
/** The list to sort */
private List list = null;
/** Xpath comparator for sorting */
private XPathComparator xpCmp = null;
public void doTag(XMLOutput output) throws Exception {
if (xpCmp == null) {
throw new MissingAttributeException( "xpCmp" );
}
if (list == null) {
throw new MissingAttributeException( "list" );
}
Collections.sort(list, xpCmp);
}
/** Set the list to sort. */
public void setList(List list) {
this.list = list;
}
/** Sets the xpath expression to use to sort selected nodes.
*/
public void setSort(XPath sortXPath) throws JaxenException {
if (xpCmp == null) xpCmp = new XPathComparator();
xpCmp.setXpath(sortXPath);
}
/**
* Set whether to sort ascending or descending.
*/
public void setDescending(boolean descending) {
if (xpCmp == null) xpCmp = new XPathComparator();
xpCmp.setDescending(descending);
}
/**
* Set the data type to convert nodes being sorted on into before sorting. This
* should be the name of a class that commons.beanutils knows how to convert strings
* into.
*/
public void setSortDataType(String sortType) throws ClassNotFoundException {
if (xpCmp == null) xpCmp = new XPathComparator();
xpCmp.setType(Class.forName(sortType));
}
}
1.4 +77 -1 jakarta-commons-sandbox/jelly/src/test/org/apache/commons/jelly/xml/suite.jelly
Index: suite.jelly
===================================================================
RCS file: /home/cvs/jakarta-commons-sandbox/jelly/src/test/org/apache/commons/jelly/xml/suite.jelly,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- suite.jelly 13 Nov 2002 19:35:14 -0000 1.3
+++ suite.jelly 27 Nov 2002 17:21:16 -0000 1.4
@@ -94,6 +94,82 @@
<test:assert xpath="$doc/*[local-name()='foo']"/>
</test:case>
+ <!-- test xpath sorting -->
+ <test:case name="testXpathSorting">
+ <x:parse var="nums">
+ <a>
+ <b v="3"/>
+ <b v="2"/>
+ <b v="1"/>
+ <b v="11"/>
+ <b v="1.4"/>
+ <b v="1.2"/>
+ </a>
+ </x:parse>
+
+ <x:parse var="deeper">
+ <a>
+ <b><c><d>3<e>1</e></d></c></b>
+ <b><c><d>2<e>11</e></d></c></b>
+ <b><c><d>1</d></c></b>
+ <b><c><d>11</d></c></b>
+ </a>
+ </x:parse>
+
+ <!-- test ascending -->
+ <j:set var="result" value=""/>
+ <x:forEach select="$nums/a/b" var="x" sort="@v">
+ <x:set var="num" select="$x/@v"/>
+ <j:set var="result" value="${result} ${num.get(0).getText()}"/>
+ </x:forEach>
+
+ <test:assertEquals expected=" 1 1.2 1.4 2 3 11" actual="${result}"/>
+
+ <!-- test descending -->
+ <j:set var="result" value=""/>
+ <x:forEach select="$nums/a/b" var="x" sort="@v" descending="true">
+ <x:set var="num" select="$x/@v"/>
+ <j:set var="result" value="${result} ${num.get(0).getText()}"/>
+ </x:forEach>
+
+ <test:assertEquals expected=" 11 3 2 1.4 1.2 1" actual="${result}"/>
+
+ <!-- test deeper nesting -->
+ <j:set var="result" value=""/>
+ <x:forEach select="$deeper/a/b" var="x" sort="c/d">
+ <j:set var="result" value="${result} ${x.getStringValue()}"/>
+ </x:forEach>
+
+ <test:assertEquals expected=" 1 211 31 11" actual="${result}"/>
+
+ <!-- test sort as strings -->
+ <j:set var="result" value=""/>
+ <x:forEach select="$nums/a/b" var="x" sort="@v" sortDataType="java.lang.String">
+ <x:set var="num" select="$x/@v"/>
+ <j:set var="result" value="${result} ${num.get(0).getText()}"/>
+ </x:forEach>
+
+ <test:assertEquals expected=" 1 1.2 1.4 11 2 3" actual="${result}"/>
+
+ <!-- test x:set with sort -->
+ <j:set var="result" value=""/>
+ <x:set var="rset" select="$nums/a/b" sort="@v"/>
+ <j:forEach var="num" items="${rset.iterator()}">
+ <j:set var="result" value="${result} ${num.attributeValue('v')}"/>
+ </j:forEach>
+
+ <test:assertEquals expected=" 1 1.2 1.4 2 3 11" actual="${result}"/>
+
+ <!-- test x:set with sort -->
+ <j:set var="result" value=""/>
+ <x:set var="rset" select="$nums/a/b"/>
+ <x:sort list="${rset}" sort="@v"/>
+ <j:forEach var="num" items="${rset.iterator()}">
+ <j:set var="result" value="${result} ${num.attributeValue('v')}"/>
+ </j:forEach>
+
+ <test:assertEquals expected=" 1 1.2 1.4 2 3 11" actual="${result}"/>
+
+ </test:case>
-
</test:suite>
1.1 jakarta-commons-sandbox/jelly/src/test/org/apache/commons/jelly/xml/xpathSortExample.jelly
Index: xpathSortExample.jelly
===================================================================
<?xml version="1.0"?>
<j:jelly xmlns:j="jelly:core" xmlns:test="jelly:junit" xmlns:x="jelly:xml">
<x:parse var="nums">
<a>
<b v="3"/>
<b v="2"/>
<b v="1"/>
<b v="11"/>
<b v="1.4"/>
<b v="1.2"/>
</a>
</x:parse>
<x:parse var="strs">
<a>
<b v="z"/>
<b v="g"/>
<b v="d"/>
<b v="a"/>
</a>
</x:parse>
<x:parse var="deeper">
<a>
<b><c><d>3<e>1</e></d></c></b>
<b><c><d>2<e>11</e></d></c></b>
<b><c><d>1</d></c></b>
<b><c><d>11</d></c></b>
</a>
</x:parse>
<j:set var="result" value=""/>
<x:forEach select="$nums/a/b" var="x" sort="@v">
<x:set var="num" select="$x/@v"/>
<j:set var="result" value="${result} ${num.get(0).getText()}"/>
</x:forEach>
${result}
<j:set var="result" value=""/>
<x:forEach select="$nums/a/b" var="x" sort="@v" descending="true">
<x:set var="num" select="$x/@v"/>
<j:set var="result" value="${result} ${num.get(0).getText()}"/>
</x:forEach>
${result}
<j:set var="result" value=""/>
<x:forEach select="$nums/a/b" var="x" sort="@v" sortDataType="java.lang.String">
<x:set var="num" select="$x/@v"/>
<j:set var="result" value="${result} ${num.get(0).getText()}"/>
</x:forEach>
${result}
<j:set var="result" value=""/>
<x:forEach select="$strs/a/b" var="x" sort="@v">
<x:set var="str" select="$x/@v"/>
<j:set var="result" value="${result} ${str.get(0).getText()}"/>
</x:forEach>
${result}
<j:set var="result" value=""/>
<x:forEach select="$deeper/a/b" var="x" sort="c/d">
<j:set var="result" value="${result} ${x.getStringValue()}"/>
</x:forEach>
${result}
<j:set var="result" value=""/>
<x:set var="rset" select="$nums/a/b" sort="@v"/>
<j:forEach var="num" items="${rset.iterator()}">
<j:set var="result" value="${result} ${num.attributeValue('v')}"/>
</j:forEach>
${result}
<j:set var="result" value=""/>
<x:set var="rset" select="$nums/a/b"/>
<x:sort list="${rset}" sort="@v"/>
<j:forEach var="num" items="${rset.iterator()}">
<j:set var="result" value="${result} ${num.attributeValue('v')}"/>
</j:forEach>
${result}
</j:jelly>
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>