package client.net.sf.saxon.ce.expr.sort; import client.net.sf.saxon.ce.Configuration; import client.net.sf.saxon.ce.expr.*; import client.net.sf.saxon.ce.lib.StringCollator; import client.net.sf.saxon.ce.om.StandardNames; import client.net.sf.saxon.ce.tree.util.URI; import client.net.sf.saxon.ce.value.Whitespace; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.type.*; import client.net.sf.saxon.ce.value.StringValue; import java.util.HashMap; /** * A SortKeyDefinition defines one component of a sort key. <BR> * * Note that most attributes defining the sort key can be attribute value templates, * and can therefore vary from one invocation to another. We hold them as expressions. As * soon as they are all known (which in general is only at run-time), the SortKeyDefinition * is replaced by a FixedSortKeyDefinition in which all these values are fixed. */ // TODO: optimise also for the case where the attributes depend only on global variables // or parameters, in which case the same AtomicComparer can be used for the duration of a // transformation. // TODO: at present the SortKeyDefinition is evaluated to obtain a AtomicComparer, which can // be used to compare two sort keys. It would be more efficient to use a Collator to // obtain collation keys for all the items to be sorted, as these can be compared more // efficiently. public class SortKeyDefinition { private static StringLiteral defaultOrder = new StringLiteral("ascending"); private static StringLiteral defaultCaseOrder = new StringLiteral("#default"); private static StringLiteral defaultLanguage = new StringLiteral(StringValue.EMPTY_STRING); protected Expression sortKey; protected Expression order = defaultOrder; protected Expression dataTypeExpression = null; // used when the type is not known till run-time protected Expression caseOrder = defaultCaseOrder; protected Expression language = defaultLanguage; protected Expression collationName = null; protected Expression stable = null; // not actually used, but present so it can be validated protected StringCollator collation; protected String baseURI; // needed in case collation URI is relative protected boolean backwardsCompatible = false; private AtomicComparer finalComparator = null; // Note, the "collation" defines the collating sequence for the sort key. The // "finalComparator" is what is actually used to do comparisons, after taking into account // ascending/descending, caseOrder, etc. /** * Set the expression used as the sort key * @param exp the sort key select expression */ public void setSortKey(Expression exp) { sortKey = exp; } /** * Get the expression used as the sort key * @return the sort key select expression */ public Expression getSortKey() { return sortKey; } /** * Set the order. This is supplied as an expression which must evaluate to "ascending" * or "descending". If the order is fixed, supply e.g. new StringValue("ascending"). * Default is "ascending". * @param exp the expression that determines the order (always a literal in XQuery, but * can be defined by an AVT in XSLT) */ public void setOrder(Expression exp) { order = exp; } /** * Get the expression that defines the order as ascending or descending * @return the expression that determines the order (always a literal in XQuery, but * can be defined by an AVT in XSLT) */ public Expression getOrder() { return order; } /** * Set the data type. This is supplied as an expression which must evaluate to "text", * "number", or a QName. If the data type is fixed, the valus should be supplied using * setDataType() and not via this method. * @param exp the expression that defines the data type, as used in XSLT 1.0 */ public void setDataTypeExpression(Expression exp) { dataTypeExpression = exp; } /** * Get the expression that defines the data type of the sort keys * @return the expression that defines the data type, as used in XSLT 1.0 */ public Expression getDataTypeExpression() { return dataTypeExpression; } /** * Set the case order. This is supplied as an expression which must evaluate to "upper-first" * or "lower-first" or "#default". If the order is fixed, supply e.g. new StringValue("lower-first"). * Default is "#default". * @param exp the expression that defines the case order */ public void setCaseOrder(Expression exp) { caseOrder = exp; } /** * Get the expression that defines the case order of the sort keys. * @return the expression that defines the case order, whose run-time value will be "upper-first", * "lower-first", or "#default". */ public Expression getCaseOrder() { return caseOrder; } /** * Set the language. This is supplied as an expression which evaluates to the language name. * If the order is fixed, supply e.g. new StringValue("de"). * @param exp the expression that determines the language */ public void setLanguage(Expression exp) { language = exp; } /** * Get the expression that defines the language of the sort keys * @return exp the expression that determines the language */ public Expression getLanguage() { return language; } /** * Set the collation name (specifically, an expression which when evaluated returns the collation URI). * @param collationName the expression that determines the collation name */ public void setCollationNameExpression(Expression collationName) { this.collationName = collationName; } /** * Get the selected collation name * (specifically, an expression which when evaluated returns the collation URI). * @return the expression that determines the collation name */ public Expression getCollationNameExpression() { return collationName; } /** * Set the collation to be used * @param collation A StringCollator, which encapsulates both the collation URI and the collating function */ public void setCollation(StringCollator collation) { this.collation = collation; } /** * Get the collation to be used * @return A StringCollator, which encapsulates both the collation URI and the collating function */ public StringCollator getCollation() { return collation; } /** * Set the base URI of the expression. This is needed to handle the case where a collation URI * evaluated at run-time turns out to be a relative URI. * @param baseURI the static base URI of the expression */ public void setBaseURI(String baseURI) { this.baseURI = baseURI; } /** * Get the static base URI of the expression. This is needed to handle the case where a collation URI * evaluated at run-time turns out to be a relative URI. * @return the static base URI of the expression */ public String getBaseURI() { return baseURI; } /** * Set whether this sort key definition is stable * @param stable the expression that determines whether the sort key definition is stable * (it evaluates to the string "yes" or "no". */ public void setStable(Expression stable) { this.stable = stable; } /** * Ask whether this sort key definition is stable * @return the expression that determines whether the sort key definition is stable * (it evaluates to the string "yes" or "no". */ public Expression getStable() { return stable; } /** * Set whether this sort key is evaluated in XSLT 1.0 backwards compatibility mode * @param compatible true if backwards compatibility mode is selected */ public void setBackwardsCompatible(boolean compatible) { backwardsCompatible = compatible; } /** * Ask whether this sort key is evaluated in XSLT 1.0 backwards compatibility mode * @return true if backwards compatibility mode was selected */ public boolean isBackwardsCompatible() { return backwardsCompatible; } /** * Ask whether the sort key definition is fixed, that is, whether all the information needed * to create a Comparator is known statically * @return true if all information needed to create a Comparator is known statically */ public boolean isFixed() { return (order instanceof Literal && (dataTypeExpression == null || dataTypeExpression instanceof Literal) && caseOrder instanceof Literal && language instanceof Literal && (stable == null || stable instanceof Literal) && (collationName == null || collationName instanceof Literal)); } /** * Simplify this sort key definition * @param visitor the expression visitor * @return the simplified sort key definition * @throws XPathException if any failure occurs */ public SortKeyDefinition simplify(ExpressionVisitor visitor) throws XPathException { sortKey = visitor.simplify(sortKey); order = visitor.simplify(order); dataTypeExpression = visitor.simplify(dataTypeExpression); caseOrder = visitor.simplify(caseOrder); language = visitor.simplify(language); stable = visitor.simplify(stable); collationName = visitor.simplify(collationName); return this; } /** * Type-check this sort key definition (all properties other than the sort key * select expression, which has a different dynamic context) * @param visitor the expression visitor * @param contextItemType the type of the context item * @throws XPathException if any failure occurs */ public void typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { order = visitor.typeCheck(order, contextItemType); dataTypeExpression = visitor.typeCheck(dataTypeExpression, contextItemType); caseOrder = visitor.typeCheck(caseOrder, contextItemType); language = visitor.typeCheck(language, contextItemType); stable = visitor.typeCheck(stable, contextItemType); collationName = visitor.typeCheck(collationName, contextItemType); if (language instanceof StringLiteral && ((StringLiteral)language).getStringValue().length() != 0) { if (!StringValue.isValidLanguageCode(((StringLiteral)language).getStringValue())) { throw new XPathException("The lang attribute of xsl:sort must be a valid language code", "XTDE0030"); } } } /** * Allocate an AtomicComparer to perform the comparisons described by this sort key component. This method * is called at run-time. The AtomicComparer takes into account not only the collation, but also parameters * such as order=descending and handling of empty sequence and NaN (the result of the compare() * method of the comparator is +1 if the second item is to sort after the first item) * @param context the dynamic evaluation context * @return an AtomicComparer suitable for making the sort comparisons */ public AtomicComparer makeComparator(XPathContext context) throws XPathException { String orderX = order.evaluateAsString(context).toString(); final Configuration config = context.getConfiguration(); final TypeHierarchy th = config.getTypeHierarchy(); AtomicComparer atomicComparer; StringCollator stringCollator; if (collation != null) { stringCollator = collation; } else if (collationName != null) { String cname = collationName.evaluateAsString(context).toString(); URI collationURI; try { collationURI = new URI(cname, true); if (!collationURI.isAbsolute()) { if (baseURI == null) { throw new XPathException("Collation URI is relative, and base URI is unknown"); } else { URI base = new URI(baseURI); collationURI = base.resolve(collationURI.toString()); } } } catch (URI.URISyntaxException err) { throw new XPathException("Collation name " + cname + " is not a valid URI: " + err); } stringCollator = context.getConfiguration().getNamedCollation(collationURI.toString()); if (stringCollator == null) { throw new XPathException("Unknown collation " + collationURI.toString(), "XTDE1035"); } } else { String caseOrderX = caseOrder.evaluateAsString(context).toString(); String languageX = language.evaluateAsString(context).toString(); HashMap props = new HashMap(); if (languageX.length() != 0 && !(language instanceof StringLiteral)) { if (!StringValue.isValidLanguageCode(((StringLiteral)language).getStringValue())) { throw new XPathException("The lang attribute of xsl:sort must be a valid language code", "XTDE0030"); } props.put("lang", languageX); } if (!caseOrderX.equals("#default")) { props.put("case-order", caseOrderX); } //stringCollator = Configuration.getPlatform().makeCollation(config, props, ""); stringCollator = null; } if (dataTypeExpression==null) { atomicComparer = AtomicSortComparer.makeSortComparer(stringCollator, sortKey.getItemType(th).getAtomizedItemType().getPrimitiveType(), context); } else { String dataType = dataTypeExpression.evaluateAsString(context).toString(); if (dataType.equals("text")) { atomicComparer = AtomicSortComparer.makeSortComparer(stringCollator, StandardNames.XS_STRING, context); atomicComparer = new TextComparer(atomicComparer); } else if (dataType.equals("number")) { atomicComparer = NumericComparer.getInstance(); } else { XPathException err = new XPathException("data-type on xsl:sort must be 'text' or 'number'"); err.setErrorCode("XTDE0030"); throw err; } } if (stable != null) { StringValue stableVal = (StringValue)stable.evaluateItem(context); String s = Whitespace.trim(stableVal.getStringValue()); if (s.equals("yes") || s.equals("no")) { // no action } else { XPathException err = new XPathException("Value of 'stable' on xsl:sort must be 'yes' or 'no'"); err.setErrorCode("XTDE0030"); throw err; } } if (orderX.equals("ascending")) { return atomicComparer; } else if (orderX.equals("descending")) { return new DescendingComparer(atomicComparer); } else { XPathException err1 = new XPathException("order must be 'ascending' or 'descending'"); err1.setErrorCode("XTDE0030"); throw err1; } } /** * Set the comparator which is used to compare two values according to this sort key. The comparator makes the final * decision whether one value sorts before or after another: this takes into account the data type, the collation, * whether empty comes first or last, whether the sort order is ascending or descending. * * <p>This method is called at compile time if all these factors are known at compile time. * It must not be called at run-time, except to reconstitute a finalComparator that has been * lost by virtue of serialization .</p> * @param comp the Atomic Comparer to be used */ public void setFinalComparator(AtomicComparer comp) { finalComparator = comp; } /** * Get the comparator which is used to compare two values according to this sort key. This method * may be called either at compile time or at run-time. If no comparator has been allocated, * it returns null. It is then necessary to allocate a comparator using the {@link #makeComparator} * method. * @return the Atomic Comparer to be used */ public AtomicComparer getFinalComparator() { return finalComparator; } } // 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.