package client.net.sf.saxon.ce.expr.instruct; import client.net.sf.saxon.ce.LogController; import client.net.sf.saxon.ce.expr.*; import client.net.sf.saxon.ce.expr.sort.*; import client.net.sf.saxon.ce.lib.StringCollator; import client.net.sf.saxon.ce.lib.TraceListener; import client.net.sf.saxon.ce.om.Item; import client.net.sf.saxon.ce.om.SequenceIterator; import client.net.sf.saxon.ce.om.StandardNames; import client.net.sf.saxon.ce.pattern.PatternSponsor; import client.net.sf.saxon.ce.trans.Err; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.tree.util.URI; import client.net.sf.saxon.ce.type.ItemType; import client.net.sf.saxon.ce.type.TypeHierarchy; import client.net.sf.saxon.ce.value.StringValue; import java.util.ArrayList; import java.util.Iterator; import com.google.gwt.logging.client.LogConfiguration; /** * 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; } /** * 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<sortKeys.length; i++) { Expression sortKey = sortKeys[i].getSortKey(); sortKey = visitor.typeCheck(sortKey, selectedItemType); if (visitor.getStaticContext().isInBackwardsCompatibleMode()) { sortKey = new FirstItemExpression(sortKey); } else { RoleLocator role = new RoleLocator(RoleLocator.INSTRUCTION, "xsl:sort/select", 0); role.setErrorCode("XTTE1020"); sortKey = CardinalityChecker.makeCardinalityChecker(sortKey, StaticProperty.ALLOWS_ZERO_OR_ONE, role); } sortKeys[i].setSortKey(sortKey); if (sortKeys[i].isFixed()) { AtomicComparer comp = sortKeys[i].makeComparator( visitor.getStaticContext().makeEarlyEvaluationContext()); sortKeys[i].setFinalComparator(comp); } else { allFixed = false; } } if (allFixed) { sortComparators = new AtomicComparer[sortKeys.length]; for (int i=0; i<sortKeys.length; i++) { sortComparators[i] = sortKeys[i].getFinalComparator(); } } } return this; } public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); select = visitor.optimize(select, contextItemType); action = action.optimize(visitor, select.getItemType(th)); key = key.optimize(visitor, select.getItemType(th)); adoptChildExpression(select); adoptChildExpression(action); adoptChildExpression(key); if (Literal.isEmptySequence(select)) { return select; } if (Literal.isEmptySequence(action)) { return action; } // Optimize the sort key definitions ItemType selectedItemType = select.getItemType(th); if (sortKeys != null) { for (int i=0; i<sortKeys.length; i++) { Expression sortKey = sortKeys[i].getSortKey(); sortKey = visitor.optimize(sortKey, selectedItemType); sortKeys[i].setSortKey(sortKey); } } if (collator == null && (collationNameExpression instanceof StringLiteral)) { String collation = ((StringLiteral)collationNameExpression).getStringValue(); URI collationURI; try { collationURI = new URI(collation, true); if (!collationURI.isAbsolute()) { URI base = new URI(baseURI); collationURI = base.resolve(collationURI.toString()); final String collationNameString = collationURI.toString(); collationNameExpression = new StringLiteral(collationNameString); collator = visitor.getConfiguration().getNamedCollation(collationNameString); if (collator == null) { dynamicError("Unknown collation " + Err.wrap(collationURI.toString(), Err.URI), "XTDE1110", null); } } } catch (URI.URISyntaxException err) { dynamicError("Collation name '" + collationNameExpression + "' is not a valid URI", "XTDE1110", null); } } return this; } /** * Get the item type of the items returned by evaluating this instruction * * @return the static item type of the instruction * @param th the type hierarchy cache */ public ItemType getItemType(TypeHierarchy th) { return action.getItemType(th); } /** * Compute the dependencies of an expression, as the union of the * dependencies of its subexpressions. (This is overridden for path expressions * and filter expressions, where the dependencies of a subexpression are not all * propogated). This method should be called only once, to compute the dependencies; * after that, getDependencies should be used. * * @return the depencies, as a bit-mask */ public int computeDependencies() { // some of the dependencies in the "action" part and in the grouping and sort keys aren't relevant, // because they don't depend on values set outside the for-each-group expression int dependencies = 0; dependencies |= select.getDependencies(); dependencies |= key.getDependencies() & ~StaticProperty.DEPENDS_ON_FOCUS; dependencies |= (action.getDependencies() &~ (StaticProperty.DEPENDS_ON_FOCUS | StaticProperty.DEPENDS_ON_CURRENT_GROUP)); if (sortKeys != null) { for (int i = 0; i < sortKeys.length; i++) { dependencies |= (sortKeys[i].getSortKey().getDependencies() &~ StaticProperty.DEPENDS_ON_FOCUS); Expression e = sortKeys[i].getCaseOrder(); if (e != null && !(e instanceof Literal)) { dependencies |= (e.getDependencies()); } e = sortKeys[i].getDataTypeExpression(); if (e != null && !(e instanceof Literal)) { dependencies |= (e.getDependencies()); } e = sortKeys[i].getLanguage(); if (e != null && !(e instanceof Literal)) { dependencies |= (e.getDependencies()); } } } if (collationNameExpression != null) { dependencies |= collationNameExpression.getDependencies(); } return dependencies; } /** * Determine whether this instruction creates new nodes. * This implementation returns true if the "action" creates new nodes. * (Nodes created by the condition can't contribute to the result). */ public final boolean createsNewNodes() { int props = action.getSpecialProperties(); return ((props & StaticProperty.NON_CREATIVE) == 0); } /** * Handle promotion offers, that is, non-local tree rewrites. * * @param offer The type of rewrite being offered * @throws XPathException */ protected void promoteInst(PromotionOffer offer) throws XPathException { select = doPromotion(select, offer); // Don't pass on other requests // TODO: promote expressions in the sort key definitions } /** * Get all the XPath expressions associated with this instruction * (in XSLT terms, the expression present on attributes of the instruction, * as distinct from the child instructions in a sequence construction) */ public Iterator<Expression> iterateSubExpressions() { ArrayList list = new ArrayList(8); list.add(select); list.add(action); list.add(key); if (collationNameExpression != null) { list.add(collationNameExpression); } if (sortKeys != null) { for (int i = 0; i < sortKeys.length; i++) { list.add(sortKeys[i].getSortKey()); Expression e = sortKeys[i].getOrder(); if (e != null) { list.add(e); } e = sortKeys[i].getCaseOrder(); if (e != null) { list.add(e); } e = sortKeys[i].getDataTypeExpression(); if (e != null) { list.add(e); } e = sortKeys[i].getLanguage(); if (e != null) { list.add(e); } e = sortKeys[i].getCollationNameExpression(); if (e != null) { list.add(e); } } } return list.iterator(); } /** * 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; } public TailCall processLeavingTail(XPathContext context) throws XPathException { GroupIterator groupIterator = getGroupIterator(context); XPathContextMajor c2 = context.newContext(); c2.setCurrentIterator(groupIterator); c2.setCurrentGroupIterator(groupIterator); c2.setCurrentTemplateRule(null); if (LogConfiguration.loggingIsEnabled() && LogController.traceIsEnabled()) { TraceListener listener = LogController.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, true); if (!collationURI.isAbsolute()) { if (baseURI == null) { dynamicError("Cannot resolve relative collation URI '" + cname + "': unknown or invalid base URI", "XTDE1110", context); } collationURI = new URI(baseURI).resolve(collationURI.toString()); cname = collationURI.toString(); } } catch (URI.URISyntaxException e) { dynamicError("Collation name '" + cname + "' is not a valid URI", "XTDE1110", context); } return context.getConfiguration().getNamedCollation(cname); } else { // Fallback - this shouldn't happen return CodepointCollator.getInstance(); } } 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.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); } 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. * <p/> * 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.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); } } // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.