All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.s9api.XPathCompiler Maven / Gradle / Ivy

There is a newer version: 10.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// 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.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.s9api;

import net.sf.saxon.expr.StaticContext;
import net.sf.saxon.expr.sort.RuleBasedSubstringMatcher;
import net.sf.saxon.expr.sort.SimpleCollation;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.lib.StringCollator;
import net.sf.saxon.sxpath.*;
import net.sf.saxon.trans.DecimalFormatManager;
import net.sf.saxon.trans.DecimalSymbols;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ValidationException;
import net.sf.saxon.value.DecimalValue;
import net.sf.saxon.value.SequenceType;

import java.net.URI;
import java.net.URISyntaxException;
import java.text.Collator;
import java.text.RuleBasedCollator;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * An XPathCompiler object allows XPath queries to be compiled. The compiler holds information that
 * represents the static context for an XPath expression.
 * 

*

To construct an XPathCompiler, use the factory method * {@link Processor#newXPathCompiler}.

*

*

An XPathCompiler may be used repeatedly to compile multiple * queries. Any changes made to the XPathCompiler (that is, to the * static context) do not affect queries that have already been compiled. * An XPathCompiler may be used concurrently in multiple threads, but * it should not then be modified once initialized.

*

*

Changes to an XPathCompiler are cumulative. There is no simple way to reset * the XPathCompiler to its initial state; instead, simply create a new * XPathCompiler.

*

*

The XPathCompiler has the ability to maintain a cache of compiled * expressions. This is active only if enabled by calling {@link #setCaching(boolean)}. * If caching is enabled, then the compiler will recognize an attempt to compile * the same expression twice, and will avoid the cost of recompiling it. The cache * is emptied by any method that changes the static context for subsequent expressions, * for example, {@link #setBaseURI(java.net.URI)}. Unless the cache is emptied, * it grows indefinitely: compiled expressions are never discarded.

* * @since 9.0 */ public class XPathCompiler { private Processor processor; private IndependentContext env; private ItemType requiredContextItemType; /*@Nullable*/ private Map cache = null; //private Map symbolsMap = new HashMap(); /** * Protected constructor * * @param processor the s9api Processor */ protected XPathCompiler(Processor processor) { this.processor = processor; env = new IndependentContext(processor.getUnderlyingConfiguration()); } /** * Get the Processor from which this XPathCompiler was constructed * * @return the Processor to which this XPathCompiler belongs * @since 9.3 */ public Processor getProcessor() { return processor; } /** * Set whether XPath 1.0 backwards compatibility mode is to be used. In backwards compatibility * mode, more implicit type conversions are allowed in XPath expressions, for example it * is possible to compare a number with a string. The default is false (backwards compatibility * mode is off). * * @param option true if XPath 1.0 backwards compatibility is to be enabled, false if it is to * be disabled. */ public void setBackwardsCompatible(boolean option) { if (cache != null) { cache.clear(); } env.setBackwardsCompatibilityMode(option); } /** * Ask whether XPath 1.0 backwards compatibility mode is in force. * * @return true if XPath 1.0 backwards compatibility is enabled, false if it is disabled. */ public boolean isBackwardsCompatible() { return env.isInBackwardsCompatibleMode(); } /** * Say whether XPath expressions compiled using this XPathCompiler are * schema-aware. They will automatically be schema-aware if the method * {@link #importSchemaNamespace(String)} is called. An XPath expression * must be marked as schema-aware if it is to handle typed (validated) * input documents. * * @param schemaAware true if expressions are to be schema-aware, false otherwise * @since 9.3 */ public void setSchemaAware(boolean schemaAware) { env.setSchemaAware(schemaAware); } /** * Ask whether XPath expressions compiled using this XPathCompiler are * schema-aware. They will automatically be schema-aware if the method * {@link #importSchemaNamespace(String)} is called. An XPath expression * must be marked as schema-aware if it is to handle typed (validated) * input documents. * * @return true if expressions are to be schema-aware, false otherwise * @since 9.3 */ public boolean isSchemaAware() { return env.isSchemaAware(); } /** * Say whether an XPath 2.0 or XPath 3.0 processor is required. * * @param version Must be numerically equal to 1.0, 2.0 or 3.0. Currently * support for XPath 3.0 is incomplete: check the release notes. *

Setting the option to 1.0 requests an XPath 2.0 processor running in 1.0 compatibility mode; * this is equivalent to setting the language version to 2.0 and backwards compatibility mode to true.

* @throws IllegalArgumentException if the version is not numerically equal to 1.0, 2.0 or 3.0. * @since 9.3 */ public void setLanguageVersion(String version) { if (cache != null) { cache.clear(); } DecimalValue dv; try { dv = (DecimalValue) DecimalValue.makeDecimalValue(version, true).asAtomic(); } catch (ValidationException err) { throw new IllegalArgumentException("Language version must be in the form of a decimal number"); } if (DecimalValue.ONE.equals(dv)) { dv = DecimalValue.TWO; env.setBackwardsCompatibilityMode(true); } if (!DecimalValue.ONE.equals(dv) && !DecimalValue.TWO.equals(dv) && !DecimalValue.THREE.equals(dv)) { throw new IllegalArgumentException("Unknown XPath language version " + version); } env.setXPathLanguageLevel(dv); } /** * Ask whether an XPath 2.0 or XPath 3.0 processor is being used * * @return version: "2.0" or "3.0" * @since 9.3 */ public String getLanguageVersion() { return (env.getXPathLanguageLevel().equals(DecimalValue.TWO) ? "2.0" : "3.0"); } /** * Set the static base URI for XPath expressions compiled using this XPathCompiler. The base URI * is part of the static context, and is used to resolve any relative URIs appearing within an XPath * expression, for example a relative URI passed as an argument to the doc() function. If no * static base URI is supplied, then the current working directory is used. * * @param uri the base URI to be set in the static context. This must be an absolute URI, or null * to indicate that no static base URI is available. */ public void setBaseURI(URI uri) { if (cache != null) { cache.clear(); } if (uri == null) { env.setBaseURI(null); } else { if (!uri.isAbsolute()) { throw new IllegalArgumentException("Supplied base URI must be absolute"); } env.setBaseURI(uri.toString()); } } /** * Get the static base URI for XPath expressions compiled using this XPathCompiler. The base URI * is part of the static context, and is used to resolve any relative URIs appearing within an XPath * expression, for example a relative URI passed as an argument to the doc() function. If no * static base URI has been explicitly set, this method returns null. * * @return the base URI from the static context */ public URI getBaseURI() { try { return new URI(env.getBaseURI()); } catch (URISyntaxException err) { throw new IllegalStateException(err); } } /** * Declare a namespace binding as part of the static context for XPath expressions compiled using this * XPathCompiler * * @param prefix The namespace prefix. If the value is a zero-length string, this method sets the default * namespace for elements and types. * @param uri The namespace URI. It is possible to specify a zero-length string to "undeclare" a namespace; * in this case the prefix will not be available for use, except in the case where the prefix * is also a zero length string, in which case the absence of a prefix implies that the name * is in no namespace. * @throws NullPointerException if either the prefix or uri is null. */ public void declareNamespace(String prefix, String uri) { if (cache != null) { cache.clear(); } env.declareNamespace(prefix, uri); } /** * Import a schema namespace: that is, add the element and attribute declarations and type definitions * contained in a given namespace to the static context for the XPath expression. *

*

This method will not cause the schema to be loaded. That must be done separately, using the * {@link SchemaManager}. This method will not fail if the schema has not been loaded (but in that case * the set of declarations and definitions made available to the XPath expression is empty). The schema * document for the specified namespace may be loaded before or after this method is called.

*

*

This method does not bind a prefix to the namespace. That must be done separately, using the * {@link #declareNamespace(String, String)} method.

* * @param uri The schema namespace to be imported. To import declarations in a no-namespace schema, * supply a zero-length string. * @since 9.1 */ public void importSchemaNamespace(String uri) { if (cache != null) { cache.clear(); } env.getImportedSchemaNamespaces().add(uri); env.setSchemaAware(true); } /** * Say whether undeclared variables are allowed. By default, they are not allowed. When * undeclared variables are allowed, it is not necessary to predeclare the variables that * may be used in the XPath expression; instead, a variable is automatically declared when a reference * to the variable is encountered within the expression. * * @param allow true if undeclared variables are allowed, false if they are not allowed. * @since 9.2 */ public void setAllowUndeclaredVariables(boolean allow) { if (cache != null) { cache.clear(); } env.setAllowUndeclaredVariables(allow); } /** * Ask whether undeclared variables are allowed. By default, they are not allowed. When * undeclared variables are allowed, it is not necessary to predeclare the variables that * may be used in the XPath expression; instead, a variable is automatically declared when a reference * to the variable is encountered within the expression. * * @return true if undeclared variables are allowed, false if they are not allowed. * @since 9.2 */ public boolean isAllowUndeclaredVariables() { return env.isAllowUndeclaredVariables(); } /** * Declare a variable as part of the static context for XPath expressions compiled using this * XPathCompiler. It is an error for the XPath expression to refer to a variable unless it has been * declared. This method declares the existence of the variable, but it does not * bind any value to the variable; that is done later, when the XPath expression is evaluated. * The variable is allowed to have any type (that is, the required type is item()*). * * @param qname The name of the variable, expressions as a QName */ public void declareVariable(QName qname) { if (cache != null) { cache.clear(); } env.declareVariable(qname.getNamespaceURI(), qname.getLocalName()); //declaredVariables.add(var); } /** * Declare a variable as part of the static context for XPath expressions compiled using this * XPathCompiler. It is an error for the XPath expression to refer to a variable unless it has been * declared. This method declares the existence of the variable, and defines the required type * of the variable, but it does not bind any value to the variable; that is done later, * when the XPath expression is evaluated. * * @param qname The name of the variable, expressed as a QName * @param itemType The required item type of the value of the variable * @param occurrences The allowed number of items in the sequence forming the value of the variable * @throws SaxonApiException if the requiredType is syntactically invalid or if it refers to namespace * prefixes or schema components that are not present in the static context */ public void declareVariable(QName qname, ItemType itemType, OccurrenceIndicator occurrences) throws SaxonApiException { if (cache != null) { cache.clear(); } XPathVariable var = env.declareVariable(qname.getNamespaceURI(), qname.getLocalName()); var.setRequiredType( SequenceType.makeSequenceType( itemType.getUnderlyingItemType(), occurrences.getCardinality())); } /** * Declare the static type of the context item. If this type is declared, and if a context item * is supplied when the query is invoked, then the context item must conform to this type (no * type conversion will take place to force it into this type). * * @param type the required type of the context item * @since 9.3 */ public void setRequiredContextItemType(ItemType type) { requiredContextItemType = type; env.setRequiredContextItemType(type.getUnderlyingItemType()); } /** * Get the required type of the context item. If no type has been explicitly declared for the context * item, an instance of AnyItemType (representing the type item()) is returned. * * @return the required type of the context item * @since 9.3 */ public ItemType getRequiredContextItemType() { return requiredContextItemType; } /** * Bind a collation URI to a collation * * @param uri the absolute collation URI * @param collation a {@link Collator} object that implements the required collation * @throws IllegalArgumentException if an attempt is made to rebind the standard URI * for the Unicode codepoint collation * @since 9.4 */ public void declareCollation(String uri, final java.text.Collator collation) { if (uri.equals(NamespaceConstant.CODEPOINT_COLLATION_URI)) { throw new IllegalArgumentException("Cannot declare the Unicode codepoint collation URI"); } StringCollator saxonCollation; if (collation instanceof RuleBasedCollator) { saxonCollation = new RuleBasedSubstringMatcher((RuleBasedCollator) collation); } else { saxonCollation = new SimpleCollation(collation); } env.getCollationMap().setNamedCollation(uri, saxonCollation); } /** * Declare the default collation * * @param uri the absolute URI of the default collation. This URI must have been bound to a collation * using the method {@link #declareCollation(String, Collator)} * @throws IllegalStateException if the collation URI has not been registered, unless it is the standard * Unicode codepoint collation which is registered implicitly * @since 9.4 */ public void declareDefaultCollation(String uri) { if (env.getCollationMap().getNamedCollation(uri) == null) { throw new IllegalStateException("Unknown collation " + uri); } env.getCollationMap().setDefaultCollationName(uri); } /** * Say whether the compiler should maintain a cache of compiled expressions. * * @param caching if set to true, caching of compiled expressions is enabled. * If set to false, any existing cache is cleared, and future compiled expressions * will not be cached until caching is re-enabled. The cache is also cleared * (but without disabling future caching) * if any method is called that changes the static context for compiling * expressions, for example {@link #declareVariable(QName)} or * {@link #declareNamespace(String, String)}. * @since 9.3 */ public void setCaching(boolean caching) { if (caching) { if (cache == null) { cache = new ConcurrentHashMap(); } } else { cache = null; } } /** * Ask whether the compiler is maintaining a cache of compiled expressions * * @return true if a cache is being maintained * @since 9.3 */ public boolean isCaching() { return cache != null; } /** * Compile an XPath expression, supplied as a character string. * * @param source A string containing the source text of the XPath expression * @return An XPathExecutable which represents the compiled xpath expression object. * The XPathExecutable may be run as many times as required, in the same or a different thread. * The XPathExecutable is not affected by any changes made to the XPathCompiler once it has been compiled. * @throws SaxonApiException if any static error is detected while analyzing the expression */ public XPathExecutable compile(String source) throws SaxonApiException { if (cache != null) { synchronized (this) { XPathExecutable expr = cache.get(source); if (expr == null) { expr = internalCompile(source); cache.put(source, expr); } return expr; } } else { return internalCompile(source); } } private XPathExecutable internalCompile(String source) throws SaxonApiException { try { env.getDecimalFormatManager().checkConsistency(); } catch (net.sf.saxon.trans.XPathException e) { throw new SaxonApiException(e); } IndependentContext ic = env; if (ic.isAllowUndeclaredVariables()) { // self-declaring variables modify the static context. The XPathCompiler must not change state // as the result of compiling an expression, so we need to copy the static context. ic = new DedicatedStaticContext(env); for (Iterator iter = env.iterateExternalVariables(); iter.hasNext(); ) { XPathVariable var = (XPathVariable) iter.next(); XPathVariable var2 = ic.declareVariable(var.getVariableQName()); var2.setRequiredType(var.getRequiredType()); } } try { XPathEvaluator eval = new XPathEvaluator(processor.getUnderlyingConfiguration()); eval.setStaticContext(ic); XPathExpression cexp = eval.createExpression(source); cexp.getInternalExpression().getExecutable().setCollationMap(env.getCollationMap()); return new XPathExecutable(cexp, processor, ic); } catch (XPathException e) { throw new SaxonApiException(e); } } /** * Compile and evaluate an XPath expression, supplied as a character string, with a given * context item. * * @param expression A string containing the source text of the XPath expression * @param contextItem The context item to be used for evaluating the expression. This * may be null if the expression is to be evaluated with no context item. * @return the result of evaluating the XPath expression with this context item. Note that * the result is an iterable, so that it can be used in a construct such as * for (XdmItem item : xpath.evaluate("//x", doc) {...} * @throws SaxonApiException if any static error is detected while analyzing the expression, * or if any dynamic error is detected while evaluating it. * @since 9.3 */ public XdmValue evaluate(String expression, /*@Nullable*/ XdmItem contextItem) throws SaxonApiException { XPathSelector selector = compile(expression).load(); if (contextItem != null) { selector.setContextItem(contextItem); } return selector.evaluate(); } /** * Compile and evaluate an XPath expression whose result is expected to be * a single item, with a given context item. The expression is supplied as * a character string. * * @param expression A string containing the source text of the XPath expression * @param contextItem The context item to be used for evaluating the expression. This * may be null if the expression is to be evaluated with no context item. * @return the result of evaluating the XPath expression with this context item. * If the result is a singleton it is returned as an XdmItem; if it is an empty * sequence, the return value is null. If the expression returns a sequence of more than one item, * any items after the first are ignored. * @throws SaxonApiException if any static error is detected while analyzing the expression, * or if any dynamic error is detected while evaluating it. * @since 9.3 */ public XdmItem evaluateSingle(String expression, /*@Nullable*/ XdmItem contextItem) throws SaxonApiException { XPathSelector selector = compile(expression).load(); if (contextItem != null) { selector.setContextItem(contextItem); } return selector.evaluateSingle(); } /** * Compile an XSLT 2.0 pattern, supplied as a character string. The compiled pattern behaves as a boolean * expression which, when evaluated in a particular context, returns true if the context node matches * the pattern, and false if it does not. An error is reported if there is no context item or it the context * item is not a node. * * @param source A string conforming to the syntax of XSLT 2.0 patterns * @return An XPathExecutable representing an expression which evaluates to true when the context node matches * the pattern, and false when it does not. * @throws SaxonApiException if the pattern contains static errors: for example, if its syntax is incorrect, * or if it refers to undeclared variables or namespaces * @since 9.1 */ public XPathExecutable compilePattern(String source) throws SaxonApiException { try { env.getDecimalFormatManager().checkConsistency(); } catch (net.sf.saxon.trans.XPathException e) { throw new SaxonApiException(e); } try { XPathEvaluator eval = new XPathEvaluator(processor.getUnderlyingConfiguration()); eval.setStaticContext(env); XPathExpression cexp = eval.createPattern(source); return new XPathExecutable(cexp, processor, env); } catch (XPathException e) { throw new SaxonApiException(e); } } /** * Registers the required decimal format properties * * @param format The name of the decimal format * @param property The decimal symbols name to update * @param value The value to update the decimal symbol property * @throws SaxonApiException if there are two conflicting definitions of the named decimal-format * @since 9.4 */ public void setDecimalFormatProperty(QName format, String property, String value) throws SaxonApiException { DecimalFormatManager dfm = env.getDecimalFormatManager(); if (dfm == null) { dfm = new DecimalFormatManager(); env.setDecimalFormatManager(dfm); } DecimalSymbols symbols = dfm.getNamedDecimalFormat(format.getStructuredQName()); try { if (property.equals("decimal-separator")) { symbols.setDecimalSeparator((value)); } else if (property.equals("grouping-separator")) { symbols.setGroupingSeparator((value)); } else if (property.equals("infinity")) { symbols.setInfinity(value); } else if (property.equals("NaN")) { symbols.setNaN(value); } else if (property.equals("minus-sign")) { symbols.setMinusSign((value)); } else if (property.equals("percent")) { symbols.setPercent((value)); } else if (property.equals("per-mille")) { symbols.setPerMille((value)); } else if (property.equals("zero-digit")) { symbols.setZeroDigit((value)); } else if (property.equals("digit")) { symbols.setDigit((value)); } else if (property.equals("pattern-separator")) { symbols.setPatternSeparator((value)); } else { throw new IllegalArgumentException("**** Unknown decimal format attribute " + property); } } catch (XPathException e) { throw new SaxonApiException(e); } } /** * Escape-hatch method to get the underlying static context object used by the implementation. * * @return the underlying static context object. In the current implementation this will always * be an instance of {@link IndependentContext}. *

*

This method provides an escape hatch to internal Saxon implementation objects that offer a finer and * lower-level degree of control than the s9api classes and methods. Some of these classes and methods may change * from release to release.

* @since 9.1 */ public StaticContext getUnderlyingStaticContext() { return env; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy