Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpelder2008-04-21 22:12:56 -0400
committerpelder2008-04-21 22:12:56 -0400
commit154bd694cd2c0fe061bb7d17c6ac7e442ec1036f (patch)
tree95cedaf9ff9ceae4a184b9e879ef387f673baddb
parente770f7b671da5d4a5f70cd42772a6cc3e29cad28 (diff)
downloadorg.eclipse.jet-154bd694cd2c0fe061bb7d17c6ac7e442ec1036f.tar.gz
org.eclipse.jet-154bd694cd2c0fe061bb7d17c6ac7e442ec1036f.tar.xz
org.eclipse.jet-154bd694cd2c0fe061bb7d17c6ac7e442ec1036f.zip
[224337] I'd need to SORT the results of a jet xpath select
-rw-r--r--doc/org.eclipse.jet.doc/references/xpathFunctions/overview.html8
-rwxr-xr-xdoc/org.eclipse.jet.doc/references/xpathFunctions/sortFunction.html93
-rw-r--r--doc/org.eclipse.jet.doc/toc.xml5
-rw-r--r--plugins/org.eclipse.jet/plugin.xml6
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathExpressionImpl.java9
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathImpl.java2
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/functions/extras/SortFunction.java171
-rw-r--r--plugins/org.eclipse.jet/src/org/eclipse/jet/xpath/Context.java95
8 files changed, 355 insertions, 34 deletions
diff --git a/doc/org.eclipse.jet.doc/references/xpathFunctions/overview.html b/doc/org.eclipse.jet.doc/references/xpathFunctions/overview.html
index d43c31e..e3bbf5f 100644
--- a/doc/org.eclipse.jet.doc/references/xpathFunctions/overview.html
+++ b/doc/org.eclipse.jet.doc/references/xpathFunctions/overview.html
@@ -96,6 +96,14 @@ The following functions are included in JET, in addition to the XPath functions
</TD>
</TR>
<TR>
+ <TD width="10%"><A href="sortFunction.html">sort</A></TD>
+ <TD>
+ <!-- Begin sort description -->
+ Sort a node set by the specified sort keys.
+ <!-- End sort description -->
+ </TD>
+ </TR>
+ <TR>
<TD width="10%"><A href="trimWhitespaceFunction.html">trimWhitespace</A></TD>
<TD>
<!-- Begin trimWhitespace description -->
diff --git a/doc/org.eclipse.jet.doc/references/xpathFunctions/sortFunction.html b/doc/org.eclipse.jet.doc/references/xpathFunctions/sortFunction.html
new file mode 100755
index 0000000..e86db70
--- /dev/null
+++ b/doc/org.eclipse.jet.doc/references/xpathFunctions/sortFunction.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<META name="GENERATOR" content="IBM Software Development Platform">
+<link rel="STYLESHEET" href="../../book.css" charset="ISO-8859-1" type="text/css"/>
+<TITLE>removeWhitespace XPath function</TITLE>
+</HEAD>
+<BODY>
+<TABLE border="0" cellpadding="0" cellspacing="1" width="100%">
+ <TBODY>
+ <TR>
+ <TH align="left"><P>sort()</P></TH>
+ <TH align="right"><A href="overview.html">Additional XPath Functions</A></TH>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<H2>sort</H2>
+<P>Sort a node set by the specified sort keys.</P>
+<HR>
+<TABLE border="0" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Function Summary<BR>&nbsp;</TH></TR>
+ <TR>
+ <TD width="20%" valign="top">minimal arguments</TD>
+ <TD><SPAN style="font-family: monospace" valign="top">
+ NodeSet sort (XPath expression, String sortKey)<BR>&nbsp;<BR>
+ </SPAN></TD>
+ </TR>
+ <TR>
+ <TD width="20%" valign="top">all arguments</TD>
+ <TD><SPAN style="font-family: monospace" valign="top">
+ NodeSet sort (XPath expression, String sortKey, String sortKey2, ...)<BR>&nbsp;<BR>
+ </SPAN></TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<TABLE border="0" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Required Arguments<BR>&nbsp;</TH>
+ </TR>
+ <TR>
+ <TD width="20%" valign="top"><B>XPath expression</B></TD>
+ <TD>an XPath expression identifying the nodes to sort</TD>
+ </TR>
+ <TR>
+ <TD width="20%" valign="top"><B>String sortKey</B></TD>
+ <TD>a quoted String containing an XPath expression for a sort key.
+ The XPath expression is evaluated relative to each node in the node set being sorted.
+ Optionally, the string my be suffixed with ::ascending or ::descending to
+ indicate sort direction. The default sort direction is ascending.</TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<TABLE border="0" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Optional arguments<BR>&nbsp;</TH>
+ </TR>
+ <TR>
+ <TD width="20%" valign="top"><B>String sortKey<i>N</i></B></TD>
+ <TD>a secondary sort key should previous keys have resulted in equality.
+ Format rules are as with sorkKey.</TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<HR>
+<TABLE border="0" width="100%">
+ <TBODY>
+ <TR>
+ <TH colspan="2" align="left">Example<BR>&nbsp;</TH></TR>
+ <TR>
+ <TD colspan="2"><SPAN style="font-family: monospace">
+<!-- Begin sort example -->
+ <B>sort</B>(/phonebook/entry, '@lastName')<br>
+ <P>returns a node set of phone book entries sorted by lastName</P>
+ <B>sort</B>(/phonebook/entry, 'substring(@lastName, 1, 1)::descending', @firstName::ascending)<br>
+ <P>returns a node set of phone book entries sorted in reverse order by the first letter of lastName
+ and, within each letter, alphabetically by firstName</P>
+<!-- End sort example -->
+</SPAN></TD>
+ </TR>
+ </TBODY>
+</TABLE>
+<P><BR>
+<FONT color="navy" size="-2">&copy; Copyright IBM Corporation 2000,
+2008. All Rights Reserved.</FONT></P></P></BODY>
+</HTML>
diff --git a/doc/org.eclipse.jet.doc/toc.xml b/doc/org.eclipse.jet.doc/toc.xml
index 6c2a5a7..013d07c 100644
--- a/doc/org.eclipse.jet.doc/toc.xml
+++ b/doc/org.eclipse.jet.doc/toc.xml
@@ -50,7 +50,10 @@
<topic label="lower-case" href="references/xpathFunctions/lower_caseFunction.html"/>
<topic label="lowercaseFirst" href="references/xpathFunctions/lowercaseFirstFunction.html"/>
<topic label="packageName" href="references/xpathFunctions/packageNameFunction.html"/>
- <topic label="removeWhitespace" href="references/xpathFunctions/removeWhitespaceFunction.html"/>
+ <topic href="references/xpathFunctions/removeWhitespaceFunction.html" label="removeWhitespace">
+ </topic>
+ <topic href="references/xpathFunctions/sortFunction.html" label="sort">
+ </topic>
<topic label="trimWhitespace" href="references/xpathFunctions/trimWhitespaceFunction.html"/>
<topic label="upper-case" href="references/xpathFunctions/upper_caseFunction.html"/>
<topic label="uppercaseFirst" href="references/xpathFunctions/uppercaseFirstFunction.html"/>
diff --git a/plugins/org.eclipse.jet/plugin.xml b/plugins/org.eclipse.jet/plugin.xml
index eb7dd83..2e2523f 100644
--- a/plugins/org.eclipse.jet/plugin.xml
+++ b/plugins/org.eclipse.jet/plugin.xml
@@ -1170,6 +1170,12 @@ if 'length' is specified, convert only the specified number of characters, other
minArgs="1"
name="isVariableDefined">
</function>
+ <function
+ implementation="org.eclipse.jet.internal.xpath.functions.extras.SortFunction"
+ maxArgs="-1"
+ minArgs="2"
+ name="sort">
+ </function>
</extension>
<extension
point="org.eclipse.ant.core.antTasks">
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathExpressionImpl.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathExpressionImpl.java
index 0f90b23..741e6f6 100644
--- a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathExpressionImpl.java
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathExpressionImpl.java
@@ -25,6 +25,7 @@ import org.eclipse.jet.xpath.Context;
import org.eclipse.jet.xpath.IAnnotationManager;
import org.eclipse.jet.xpath.NodeSet;
import org.eclipse.jet.xpath.XPathExpression;
+import org.eclipse.jet.xpath.XPathFunctionResolver;
import org.eclipse.jet.xpath.XPathVariableResolver;
@@ -41,22 +42,26 @@ public class XPathExpressionImpl implements XPathExpression
private final IAnnotationManager annotationManager;
+ private final XPathFunctionResolver functionResolver;
+
/**
* @param node The parsed expression
* @param variableResolver the variable resolver
+ * @param functionResolver
*
*/
- public XPathExpressionImpl(ExprNode node, XPathVariableResolver variableResolver, IAnnotationManager annotationManager)
+ public XPathExpressionImpl(ExprNode node, XPathVariableResolver variableResolver, XPathFunctionResolver functionResolver, IAnnotationManager annotationManager)
{
super();
this.node = node;
this.variableResolver = variableResolver;
+ this.functionResolver = functionResolver;
this.annotationManager = annotationManager;
}
public Object evaluate(Object contextObject)
{
- return node.evalAsObject(new Context(contextObject, variableResolver, annotationManager));
+ return node.evalAsObject(new Context(contextObject, variableResolver, annotationManager, functionResolver));
}
/* (non-Javadoc)
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathImpl.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathImpl.java
index aeaca9f..5ae6e0d 100644
--- a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathImpl.java
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/XPathImpl.java
@@ -104,7 +104,7 @@ public class XPathImpl implements XPath
}
// FIXME Do a semantic analysis of expr to detect semantic errors not found in parsing.
- XPathExpressionImpl pathExpr = new XPathExpressionImpl(expr, variableResolver, annotationManager);
+ XPathExpressionImpl pathExpr = new XPathExpressionImpl(expr, variableResolver, functionResolver, annotationManager);
return pathExpr;
}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/functions/extras/SortFunction.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/functions/extras/SortFunction.java
new file mode 100644
index 0000000..a7d2fd7
--- /dev/null
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/internal/xpath/functions/extras/SortFunction.java
@@ -0,0 +1,171 @@
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM - Initial API and implementation
+ *
+ * </copyright>
+ *
+ * $Id: SortFunction.java,v 1.1 2008/04/22 02:12:56 pelder Exp $
+ */
+package org.eclipse.jet.internal.xpath.functions.extras;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.eclipse.jet.xpath.Context;
+import org.eclipse.jet.xpath.XPath;
+import org.eclipse.jet.xpath.XPathException;
+import org.eclipse.jet.xpath.XPathExpression;
+import org.eclipse.jet.xpath.XPathFactory;
+import org.eclipse.jet.xpath.XPathFunction;
+import org.eclipse.jet.xpath.XPathFunctionWithContext;
+import org.eclipse.jet.xpath.XPathRuntimeException;
+import org.eclipse.jet.xpath.XPathUtil;
+
+/**
+ * Implementation of sort(NodeSet,String key) XPath function
+ */
+public class SortFunction implements XPathFunction, XPathFunctionWithContext
+{
+
+ private static class XPathComparator implements Comparator
+ {
+ private final XPathExpression xpathExpr;
+
+ public XPathComparator(XPathExpression xpathExpr)
+ {
+ this.xpathExpr = xpathExpr;
+ }
+
+ public int compare(Object o1, Object o2)
+ {
+ Object r1 = xpathExpr.evaluate(o1);
+ Object r2 = xpathExpr.evaluate(o2);
+ if(r1 instanceof Comparable) {
+ return ((Comparable)r1).compareTo(r2);
+ } else {
+ // not comparable. Try casting to strings...
+ String s1 = XPathUtil.xpathString(r1);
+ String s2 = XPathUtil.xpathString(r2);
+ return s1.compareTo(s2);
+ }
+ }
+ }
+
+ private static class DescendingXPathComparator extends XPathComparator
+ {
+
+ public DescendingXPathComparator(XPathExpression xpathExpr)
+ {
+ super(xpathExpr);
+ }
+
+ public int compare(Object o1, Object o2)
+ {
+ return super.compare(o2, o1);
+ }
+
+ }
+
+ private static class ChainedComparator implements Comparator
+ {
+ private final Comparator[] comparatorChain;
+
+ public ChainedComparator(Comparator[] comparatorChain)
+ {
+ this.comparatorChain = comparatorChain;
+
+ }
+
+ public int compare(Object o1, Object o2)
+ {
+ for (int i = 0; i < comparatorChain.length; i++)
+ {
+ int result = comparatorChain[i].compare(o1, o2);
+ if(result != 0) {
+ return result;
+ }
+ }
+ return 0;
+ }
+
+ }
+ private Context context;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jet.xpath.XPathFunction#evaluate(java.util.List)
+ */
+ public Object evaluate(List args)
+ {
+ Object rawNodeSet = args.get(0);
+ List nodes;
+ if(rawNodeSet instanceof Collection) {
+ nodes = new ArrayList((Collection)rawNodeSet);
+ } else if(rawNodeSet.getClass().isArray()) {
+ nodes = Arrays.asList((Object[])rawNodeSet);
+ } else {
+ // only one object, don't need to sort it at all.
+ return Collections.singleton(rawNodeSet);
+ }
+ try
+ {
+ Comparator comparator = createComparator(args.subList(1, args.size()));
+
+ Collections.sort(nodes, comparator);
+ return nodes;
+ }
+ catch (XPathException e)
+ {
+ throw new XPathRuntimeException(e);
+ }
+ }
+
+ private Comparator createComparator(List keyExprList) throws XPathException
+ {
+ final XPath xpath = createXPathEnv();
+
+ final Comparator[] comparators = new Comparator[keyExprList.size()];
+
+ for (int i = 0; i < comparators.length; i++)
+ {
+ final String keyExpr = XPathUtil.xpathString(keyExprList.get(i));
+ final int orderIdx = keyExpr.lastIndexOf("::"); //$NON-NLS-1$
+ final String ordering = orderIdx >= 0 ? keyExpr.substring(orderIdx) : null;
+ if("::descending".equalsIgnoreCase(ordering)) { //$NON-NLS-1$
+ comparators[i] = new DescendingXPathComparator(xpath.compile(keyExpr.substring(0, orderIdx)));
+ } else if("::ascending".equalsIgnoreCase(ordering)) { //$NON-NLS-1$
+ comparators[i] = new XPathComparator(xpath.compile(keyExpr.substring(0, orderIdx)));
+ } else {
+ comparators[i] = new XPathComparator(xpath.compile(keyExpr));
+ }
+ }
+
+
+ return comparators.length == 1 ? comparators[0] : new ChainedComparator(comparators);
+ }
+
+ private XPath createXPathEnv()
+ {
+ XPath xpath = XPathFactory.newInstance().newXPath(context.getAnnotationManager());
+ xpath.setXPathVariableResolver(context.getVariableResolver());
+ xpath.setXPathFunctionResolver(context.getFunctionResolver());
+ return xpath;
+ }
+
+ public void setContext(Context context)
+ {
+ this.context = context;
+ }
+
+}
diff --git a/plugins/org.eclipse.jet/src/org/eclipse/jet/xpath/Context.java b/plugins/org.eclipse.jet/src/org/eclipse/jet/xpath/Context.java
index 93cede9..7564726 100644
--- a/plugins/org.eclipse.jet/src/org/eclipse/jet/xpath/Context.java
+++ b/plugins/org.eclipse.jet/src/org/eclipse/jet/xpath/Context.java
@@ -30,6 +30,16 @@ import org.eclipse.jet.xpath.inspector.InspectorManager;
public final class Context
{
+ private static DefaultXPathFunctionResolver defaultXPathFunctionResolver;
+
+ private static DefaultXPathFunctionResolver getDefaultFunctionResolver()
+ {
+ if(defaultXPathFunctionResolver == null) {
+ defaultXPathFunctionResolver = new DefaultXPathFunctionResolver();
+ }
+ return defaultXPathFunctionResolver;
+ }
+
private final Object contextNode;
private final int contextPosition;
@@ -42,14 +52,27 @@ public final class Context
private final IAnnotationManager annotationManager;
+ private final XPathFunctionResolver functionResolver;
+
+ public Context(Object contextNode, IAnnotationManager annotationManager)
+ {
+ this(contextNode, Collections.EMPTY_MAP, annotationManager);
+ }
+
/**
- *
+ * @param contextNode
+ * @param contextPosition
+ * @param contextSize
+ * @param variableResolver
+ * @param functionResolver
+ * @param annotationManager
*/
private Context(
Object contextNode,
int contextPosition,
int contextSize,
XPathVariableResolver variableResolver,
+ XPathFunctionResolver functionResolver,
IAnnotationManager annotationManager)
{
super();
@@ -57,6 +80,7 @@ public final class Context
this.contextPosition = contextPosition;
this.contextSize = contextSize;
this.variableResolver = variableResolver;
+ this.functionResolver = functionResolver;
this.annotationManager = annotationManager;
inspectorManager = InspectorManager.getInstance();
}
@@ -76,47 +100,63 @@ public final class Context
public Context(Object contextNode, XPathVariableResolver variableResolver, IAnnotationManager annotationManager)
{
- this(contextNode, 1, 1, variableResolver, annotationManager);
+ this(contextNode, variableResolver, annotationManager, getDefaultFunctionResolver());
}
- public Context(Object contextNode, IAnnotationManager annotationManager)
+ public Context(Object contextNode, XPathVariableResolver variableResolver, IAnnotationManager annotationManager, XPathFunctionResolver functionResolver)
{
- this(contextNode, Collections.EMPTY_MAP, annotationManager);
+ this(
+ contextNode,
+ 1,
+ 1,
+ variableResolver,
+ functionResolver == null ? getDefaultFunctionResolver() : functionResolver,
+ annotationManager);
}
- public int getContextPosition()
+ /**
+ * Return the context's annotation manager
+ * @return the annotation manager
+ * @throws IllegalStateException if the context has not annotation manager
+ * @see #hasAnnotationManager()
+ */
+ public IAnnotationManager getAnnotationManager()
{
- return contextPosition;
+ if (annotationManager == null)
+ {
+ throw new IllegalStateException();
+ }
+ return annotationManager;
}
- public int getContextSize()
+ public Object getContextNode()
{
- return contextSize;
+ return contextNode;
}
- public Object getContextNode()
+ public INodeInspector getContextNodeInspector()
{
- return contextNode;
+ return inspectorManager.getInspector(contextNode);
}
- public Context newSubContext(Object contextNode2, int contextPosition2, int contextSize2)
+ public int getContextPosition()
{
- return new Context(contextNode2, contextPosition2, contextSize2, variableResolver, annotationManager);
+ return contextPosition;
}
- public INodeInspector getContextNodeInspector()
+ public int getContextSize()
{
- return inspectorManager.getInspector(contextNode);
+ return contextSize;
}
- public XPathVariableResolver getVariableResolver()
+ public XPathFunctionResolver getFunctionResolver()
{
- return variableResolver;
+ return functionResolver;
}
- public String toString()
+ public XPathVariableResolver getVariableResolver()
{
- return "Context(" + contextNode.toString() + "," + contextPosition + "," + contextSize + ")"; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$
+ return variableResolver;
}
/**
@@ -128,18 +168,13 @@ public final class Context
return annotationManager != null;
}
- /**
- * Return the context's annotation manager
- * @return the annotation manager
- * @throws IllegalStateException if the context has not annotation manager
- * @see #hasAnnotationManager()
- */
- public IAnnotationManager getAnnotationManager()
+ public Context newSubContext(Object contextNode2, int contextPosition2, int contextSize2)
{
- if (annotationManager == null)
- {
- throw new IllegalStateException();
- }
- return annotationManager;
+ return new Context(contextNode2, contextPosition2, contextSize2, variableResolver, functionResolver, annotationManager);
+ }
+
+ public String toString()
+ {
+ return "Context(" + contextNode.toString() + "," + contextPosition + "," + contextSize + ")"; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$
}
}

Back to the top