net.sf.saxon.instruct.ForEachGroup Maven / Gradle / Ivy
Show all versions of saxon9 Show documentation
package net.sf.saxon.instruct;
import net.sf.saxon.Controller;
import net.sf.saxon.Err;
import net.sf.saxon.expr.*;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.pattern.PatternSponsor;
import net.sf.saxon.sort.*;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trace.TraceListener;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.SchemaType;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.StringValue;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Handler for xsl:for-each-group elements in stylesheet. This is a new instruction
* defined in XSLT 2.0
*/
public class ForEachGroup extends Instruction
implements ContextMappingFunction, SortKeyEvaluator {
public static final int GROUP_BY = 0;
public static final int GROUP_ADJACENT = 1;
public static final int GROUP_STARTING = 2;
public static final int GROUP_ENDING = 3;
private Expression select;
private Expression action;
private byte algorithm;
private Expression key; // for group-starting and group-ending, this is a PatternSponsor
private Expression collationNameExpression;
private String baseURI;
private StringCollator collator = null; // collation used for the grouping comparisons
private SortKeyDefinition[] sortKeys = null;
private transient AtomicComparer[] sortComparators = null; // comparators used for sorting the groups
/**
* Create a for-each-group instruction
* @param select the select expression (selects the population to be grouped)
* @param action the body of the for-each-group (applied to each group in turn)
* @param algorithm one of group-by, group-adjacent, group-starting-with, group-ending-with
* @param key expression to evaluate the grouping key
* @param collator user for comparing strings
* @param collationNameExpression expression that yields the name of the collation to be used
* @param baseURI static base URI of the expression
* @param sortKeys list of xsl:sort keys for sorting the groups
*/
public ForEachGroup(Expression select,
Expression action,
byte algorithm,
Expression key,
StringCollator collator,
Expression collationNameExpression,
String baseURI,
SortKeyDefinition[] sortKeys) {
this.select = select;
this.action = action;
this.algorithm = algorithm;
this.key = key;
this.collator = collator;
this.collationNameExpression = collationNameExpression;
this.baseURI = baseURI;
this.sortKeys = sortKeys;
Iterator kids = iterateSubExpressions();
while (kids.hasNext()) {
Expression child = (Expression)kids.next();
adoptChildExpression(child);
}
}
/**
* Get the name of this instruction for diagnostic and tracing purposes
* @return the name of the instruction
*/
public int getInstructionNameCode() {
return StandardNames.XSL_FOR_EACH_GROUP;
}
/**
* Get the action expression (the content of the for-each-group)
* @return the body of the xsl:for-each-group instruction
*/
public Expression getActionExpression() {
return action;
}
/**
* Get the grouping key expression expression (the group-by or group-adjacent expression, or a
* PatternSponsor containing the group-starting-with or group-ending-with expression)
* @return the expression used to calculate grouping keys
*/
public Expression getGroupingKey() {
return key;
}
/**
* Simplify an expression. This performs any static optimization (by rewriting the expression
* as a different expression).
*
* @return the simplified expression
* @throws XPathException if an error is discovered during expression
* rewriting
* @param visitor an expression visitor
*/
public Expression simplify(ExpressionVisitor visitor) throws XPathException {
select = visitor.simplify(select);
action = visitor.simplify(action);
key = visitor.simplify(key);
return this;
}
public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy();
select = visitor.typeCheck(select, contextItemType);
ItemType selectedItemType = select.getItemType(th);
action = visitor.typeCheck(action, selectedItemType);
key = visitor.typeCheck(key, selectedItemType);
if (Literal.isEmptySequence(select)) {
return select;
}
if (Literal.isEmptySequence(action)) {
return action;
}
if (sortKeys != null) {
boolean allFixed = true;
for (int i=0; i
* The default implementation of this method assumes that an expression does no navigation other than
* the navigation done by evaluating its subexpressions, and that the subexpressions are evaluated in the
* same context as the containing expression. The method must be overridden for any expression
* where these assumptions do not hold. For example, implementations exist for AxisExpression, ParentExpression,
* and RootExpression (because they perform navigation), and for the doc(), document(), and collection()
* functions because they create a new navigation root. Implementations also exist for PathExpression and
* FilterExpression because they have subexpressions that are evaluated in a different context from the
* calling expression.
*
* @param pathMap the PathMap to which the expression should be added
* @param pathMapNodeSet
* @return the pathMapNode representing the focus established by this expression, in the case where this
* expression is the first operand of a path expression or filter expression. For an expression that does
* navigation, it represents the end of the arc in the path map that describes the navigation route. For other
* expressions, it is the same as the input pathMapNode.
*/
public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, PathMap.PathMapNodeSet pathMapNodeSet) {
PathMap.PathMapNodeSet target = select.addToPathMap(pathMap, pathMapNodeSet);
if (collationNameExpression != null) {
collationNameExpression.addToPathMap(pathMap, pathMapNodeSet);
}
if (sortKeys != null) {
for (int i = 0; i < sortKeys.length; i++) {
sortKeys[i].getSortKey().addToPathMap(pathMap, target);
Expression e = sortKeys[i].getOrder();
if (e != null) {
e.addToPathMap(pathMap, pathMapNodeSet);
}
e = sortKeys[i].getCaseOrder();
if (e != null) {
e.addToPathMap(pathMap, pathMapNodeSet);
}
e = sortKeys[i].getDataTypeExpression();
if (e != null) {
e.addToPathMap(pathMap, pathMapNodeSet);
}
e = sortKeys[i].getLanguage();
if (e != null) {
e.addToPathMap(pathMap, pathMapNodeSet);
}
e = sortKeys[i].getCollationNameExpression();
if (e != null) {
e.addToPathMap(pathMap, pathMapNodeSet);
}
}
}
return action.addToPathMap(pathMap, target);
}
/**
* Given an expression that is an immediate child of this expression, test whether
* the evaluation of the parent expression causes the child expression to be
* evaluated repeatedly
* @param child the immediate subexpression
* @return true if the child expression is evaluated repeatedly
*/
public boolean hasLoopingSubexpression(Expression child) {
return child == action || child == key;
}
/**
* Replace one subexpression by a replacement subexpression
* @param original the original subexpression
* @param replacement the replacement subexpression
* @return true if the original subexpression is found
*/
public boolean replaceSubExpression(Expression original, Expression replacement) {
boolean found = false;
if (select == original) {
select = replacement;
found = true;
}
if (action == original) {
action = replacement;
found = true;
}
if (collationNameExpression == original) {
collationNameExpression = replacement;
found = true;
}
if (key == original) {
key = replacement;
found = true;
}
if (sortKeys != null) {
for (int i = 0; i < sortKeys.length; i++) {
if (sortKeys[i].getSortKey() == original) {
sortKeys[i].setSortKey(replacement);
found = true;
}
if (sortKeys[i].getOrder() == original) {
sortKeys[i].setOrder(replacement);
found = true;
}
if (sortKeys[i].getCaseOrder() == original) {
sortKeys[i].setCaseOrder(replacement);
found = true;
}
if (sortKeys[i].getDataTypeExpression() == original) {
sortKeys[i].setDataTypeExpression(replacement);
found = true;
}
if (sortKeys[i].getLanguage() == original) {
sortKeys[i].setLanguage(replacement);
found = true;
}
}
}
return found;
}
/**
* Check that any elements and attributes constructed or returned by this expression are acceptable
* in the content model of a given complex type. It's always OK to say yes, since the check will be
* repeated at run-time. The process of checking element and attribute constructors against the content
* model of a complex type also registers the type of content expected of those constructors, so the
* static validation can continue recursively.
*/
public void checkPermittedContents(SchemaType parentType, StaticContext env, boolean whole) throws XPathException {
action.checkPermittedContents(parentType, env, false);
}
public TailCall processLeavingTail(XPathContext context) throws XPathException {
Controller controller = context.getController();
GroupIterator groupIterator = getGroupIterator(context);
XPathContextMajor c2 = context.newContext();
c2.setOrigin(this);
c2.setCurrentIterator(groupIterator);
c2.setCurrentGroupIterator(groupIterator);
c2.setCurrentTemplateRule(null);
if (controller.isTracing()) {
TraceListener listener = controller.getTraceListener();
while (true) {
Item item = groupIterator.next();
if (item == null) {
break;
}
listener.startCurrentItem(item);
action.process(c2);
listener.endCurrentItem(item);
}
} else {
while (true) {
Item item = groupIterator.next();
if (item == null) {
break;
}
action.process(c2);
}
}
return null;
}
/**
* Get (and if necessary, create) the comparator used for comparing grouping key values
* @param context XPath dynamic context
* @return a StringCollator suitable for comparing the values of grouping keys
* @throws XPathException
*/
private StringCollator getCollator(XPathContext context) throws XPathException {
if (collationNameExpression != null) {
StringValue collationValue = (StringValue)collationNameExpression.evaluateItem(context);
String cname = collationValue.getStringValue();
URI collationURI;
try {
collationURI = new URI(cname);
if (!collationURI.isAbsolute()) {
if (baseURI == null) {
XPathException err = new XPathException("Cannot resolve relative collation URI '" + cname +
"': unknown or invalid base URI");
err.setErrorCode("XTDE1110");
err.setXPathContext(context);
err.setLocator(this);
throw err;
}
collationURI = new URI(baseURI).resolve(collationURI);
cname = collationURI.toString();
}
} catch (URISyntaxException e) {
XPathException err = new XPathException("Collation name '" + cname + "' is not a valid URI");
err.setErrorCode("XTDE1110");
err.setXPathContext(context);
err.setLocator(this);
throw err;
}
return context.getCollation(cname);
} else {
StringCollator collator = context.getDefaultCollation();
return (collator==null ? CodepointCollator.getInstance() : collator);
}
}
private GroupIterator getGroupIterator(XPathContext context) throws XPathException {
SequenceIterator population = select.iterate(context);
// get an iterator over the groups in "order of first appearance"
GroupIterator groupIterator;
switch (algorithm) {
case GROUP_BY: {
StringCollator coll = collator;
if (coll==null) {
// The collation is determined at run-time
coll = getCollator(context);
}
XPathContext c2 = context.newMinorContext();
c2.setOrigin(this);
c2.setCurrentIterator(population);
groupIterator = new GroupByIterator(population, key, c2, coll);
break;
}
case GROUP_ADJACENT: {
StringCollator coll = collator;
if (coll==null) {
// The collation is determined at run-time
coll = getCollator(context);
}
groupIterator = new GroupAdjacentIterator(population, key, context, coll);
break;
}
case GROUP_STARTING:
groupIterator = new GroupStartingIterator(population,
((PatternSponsor)key).getPattern(),
context);
break;
case GROUP_ENDING:
groupIterator = new GroupEndingIterator(population,
((PatternSponsor)key).getPattern(),
context);
break;
default:
throw new AssertionError("Unknown grouping algorithm");
}
// now iterate over the leading nodes of the groups
if (sortKeys != null) {
AtomicComparer[] comps = sortComparators;
XPathContext xpc = context.newMinorContext();
if (comps == null) {
comps = new AtomicComparer[sortKeys.length];
for (int s = 0; s < sortKeys.length; s++) {
comps[s] = sortKeys[s].makeComparator(xpc);
}
}
groupIterator = new SortedGroupIterator(xpc, groupIterator, this, comps, this);
}
return groupIterator;
}
/**
* Return an Iterator to iterate over the values of a sequence. The value of every
* expression can be regarded as a sequence, so this method is supported for all
* expressions. This default implementation relies on the process() method: it
* "pushes" the results of the instruction to a sequence in memory, and then
* iterates over this in-memory sequence.
*
* In principle instructions should implement a pipelined iterate() method that
* avoids the overhead of intermediate storage.
*
* @param context supplies the context for evaluation
* @return a SequenceIterator that can be used to iterate over the result
* of the expression
* @throws XPathException if any dynamic error occurs evaluating the
* expression
*/
public SequenceIterator iterate(XPathContext context) throws XPathException {
GroupIterator master = getGroupIterator(context);
XPathContextMajor c2 = context.newContext();
c2.setOrigin(this);
c2.setCurrentIterator(master);
c2.setCurrentGroupIterator(master);
c2.setCurrentTemplateRule(null);
return new ContextMappingIterator(this, c2);
}
/**
* Map one item to a sequence.
*
* @param context The processing context. This is supplied only for mapping constructs that
* set the context node, position, and size. Otherwise it is null.
* @return either (a) a SequenceIterator over the sequence of items that the supplied input
* item maps to, or (b) an Item if it maps to a single item, or (c) null if it maps to an empty
* sequence.
*/
public SequenceIterator map(XPathContext context) throws XPathException {
return action.iterate(context);
}
/**
* Callback for evaluating the sort keys
*/
public Item evaluateSortKey(int n, XPathContext c) throws XPathException {
return sortKeys[n].getSortKey().evaluateItem(c);
}
/**
* Diagnostic print of expression structure. The abstract expression tree
* is written to the supplied output destination.
*/
public void explain(ExpressionPresenter out) {
out.startElement("forEachGroup");
out.emitAttribute("algorithm", getAlgorithmName(algorithm));
out.startSubsidiaryElement("select");
select.explain(out);
out.endSubsidiaryElement();
out.startSubsidiaryElement("key");
key.explain(out);
out.endSubsidiaryElement();
out.startSubsidiaryElement("return");
action.explain(out);
out.endSubsidiaryElement();
out.endElement();
}
private String getAlgorithmName(byte algorithm) {
switch (algorithm) {
case GROUP_BY:
return "group-by";
case GROUP_ADJACENT:
return "group-adjacent";
case GROUP_STARTING:
return "group-starting-with";
case GROUP_ENDING:
return "group-ending-with";
default:
return "** unknown algorithm **";
}
}
}
//
// The contents of this file are subject to the Mozilla Public License Version 1.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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is Michael H. Kay.
//
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
//
// Contributor(s): none.
//