package client.net.sf.saxon.ce.expr.instruct; import client.net.sf.saxon.ce.expr.*; 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.EmptySequenceTest; import client.net.sf.saxon.ce.regex.ARegularExpression; import client.net.sf.saxon.ce.regex.RegexIterator; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.tree.iter.EmptyIterator; import client.net.sf.saxon.ce.type.BuiltInAtomicType; import client.net.sf.saxon.ce.type.ItemType; import client.net.sf.saxon.ce.type.Type; import client.net.sf.saxon.ce.type.TypeHierarchy; import client.net.sf.saxon.ce.value.SequenceType; import java.util.ArrayList; import java.util.Iterator; /** * An xsl:analyze-string element in the stylesheet. New at XSLT 2.0 */ public class AnalyzeString extends Instruction { private Expression select; private Expression regex; private Expression flags; private Expression matching; private Expression nonMatching; /** * Construct an AnalyzeString instruction * * @param select the expression containing the input string * @param regex the regular expression * @param flags the flags parameter * @param matching actions to be applied to a matching substring * @param nonMatching actions to be applied to a non-matching substring */ public AnalyzeString(Expression select, Expression regex, Expression flags, Expression matching, Expression nonMatching) { this.select = select; this.regex = regex; this.flags = flags; this.matching = matching; this.nonMatching = nonMatching; Iterator kids = iterateSubExpressions(); while (kids.hasNext()) { Expression child = (Expression) kids.next(); adoptChildExpression(child); } } public int getInstructionNameCode() { return StandardNames.XSL_ANALYZE_STRING; } /** * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process(). * This method indicates which of these methods is prefered. */ public int getImplementationMethod() { return Expression.PROCESS_METHOD | Expression.ITERATE_METHOD; } /** * Get the expression used to process matching substrings * * @return the expression used to process matching substrings */ public Expression getMatchingExpression() { return matching; } /** * Get the expression used to process non-matching substrings * * @return the expression used to process non-matching substrings */ public Expression getNonMatchingExpression() { return nonMatching; } /** * Simplify an expression. This performs any static optimization (by rewriting the expression * as a different expression). * * @param visitor an expression visitor * @return the simplified expression * @throws XPathException if an error is discovered during expression * rewriting */ public Expression simplify(ExpressionVisitor visitor) throws XPathException { select = visitor.simplify(select); regex = visitor.simplify(regex); flags = visitor.simplify(flags); matching = visitor.simplify(matching); nonMatching = visitor.simplify(nonMatching); return this; } public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { select = visitor.typeCheck(select, contextItemType); adoptChildExpression(select); regex = visitor.typeCheck(regex, contextItemType); adoptChildExpression(regex); flags = visitor.typeCheck(flags, contextItemType); adoptChildExpression(flags); if (matching != null) { matching = visitor.typeCheck(matching, BuiltInAtomicType.STRING); adoptChildExpression(matching); } if (nonMatching != null) { nonMatching = visitor.typeCheck(nonMatching, BuiltInAtomicType.STRING); adoptChildExpression(nonMatching); } RoleLocator role = new RoleLocator(RoleLocator.INSTRUCTION, "analyze-string/select", 0); SequenceType required = (SequenceType.SINGLE_STRING); // see bug 7976 select = TypeChecker.staticTypeCheck(select, required, false, role, visitor); role = new RoleLocator(RoleLocator.INSTRUCTION, "analyze-string/regex", 0); regex = TypeChecker.staticTypeCheck(regex, SequenceType.SINGLE_STRING, false, role, visitor); role = new RoleLocator(RoleLocator.INSTRUCTION, "analyze-string/flags", 0); flags = TypeChecker.staticTypeCheck(flags, SequenceType.SINGLE_STRING, false, role, visitor); return this; } public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { select = visitor.optimize(select, contextItemType); adoptChildExpression(select); regex = visitor.optimize(regex, contextItemType); adoptChildExpression(regex); flags = visitor.optimize(flags, contextItemType); adoptChildExpression(flags); if (matching != null) { matching = matching.optimize(visitor, BuiltInAtomicType.STRING); adoptChildExpression(matching); } if (nonMatching != null) { nonMatching = nonMatching.optimize(visitor, BuiltInAtomicType.STRING); adoptChildExpression(nonMatching); } return this; } /** * Get the item type of the items returned by evaluating this instruction * * @param th the type hierarchy cache * @return the static item type of the instruction */ public ItemType getItemType(TypeHierarchy th) { if (matching != null) { if (nonMatching != null) { return Type.getCommonSuperType(matching.getItemType(th), nonMatching.getItemType(th), th); } else { return matching.getItemType(th); } } else { if (nonMatching != null) { return nonMatching.getItemType(th); } else { return EmptySequenceTest.getInstance(); } } } /** * 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 |= regex.getDependencies(); dependencies |= flags.getDependencies(); if (matching != null) { dependencies |= (matching.getDependencies() & ~ (StaticProperty.DEPENDS_ON_FOCUS | StaticProperty.DEPENDS_ON_REGEX_GROUP)); } if (nonMatching != null) { dependencies |= (nonMatching.getDependencies() & ~ (StaticProperty.DEPENDS_ON_FOCUS | StaticProperty.DEPENDS_ON_REGEX_GROUP)); } return dependencies; } /** * 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); regex = doPromotion(regex, offer); flags = doPromotion(flags, offer); if (matching != null) { matching = doPromotion(matching, offer); } if (nonMatching != null) { nonMatching = doPromotion(nonMatching, offer); } } /** * 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<Expression> list = new ArrayList(5); list.add(select); list.add(regex); list.add(flags); if (matching != null) { list.add(matching); } if (nonMatching != null) { list.add(nonMatching); } 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 == matching || child == nonMatching; } /** * 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 (regex == original) { regex = replacement; found = true; } if (flags == original) { flags = replacement; found = true; } if (matching == original) { matching = replacement; found = true; } if (nonMatching == original) { nonMatching = replacement; found = true; } return found; } /** * ProcessLeavingTail: called to do the real work of this instruction. This method * must be implemented in each subclass. The results of the instruction are written * to the current Receiver, which can be obtained via the Controller. * * @param context The dynamic context of the transformation, giving access to the current node, * the current variables, etc. * @return null if the instruction has completed execution; or a TailCall indicating * a function call or template call that is delegated to the caller, to be made after the stack has * been unwound so as to save stack space. */ public TailCall processLeavingTail(XPathContext context) throws XPathException { RegexIterator iter = getRegexIterator(context); XPathContextMajor c2 = context.newContext(); c2.setCurrentIterator(iter); c2.setCurrentRegexIterator(iter); while (true) { Item it = iter.next(); if (it == null) { break; } if (iter.isMatching()) { if (matching != null) { matching.process(c2); } } else { if (nonMatching != null) { nonMatching.process(c2); } } } return null; } /** * Get an iterator over the substrings defined by the regular expression * * @param context the evaluation context * @return an iterator that returns matching and nonmatching substrings * @throws XPathException if a dynamic error occurs */ private RegexIterator getRegexIterator(XPathContext context) throws XPathException { CharSequence input = select.evaluateAsString(context); String flagstr = flags.evaluateAsString(context).toString(); ARegularExpression re = new ARegularExpression(regex.evaluateAsString(context), flagstr, "XP20", null); if (re.matches("")) { dynamicError("The regular expression must not be one that matches a zero-length string", "XTDE1150", context); } return re.analyze(input); } /** * 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 handles iteration for expressions that * return singleton values: for non-singleton expressions, the subclass must * provide its own implementation. * * @param context supplies the context for evaluation * @return a SequenceIterator that can be used to iterate over the result * of the expression * @throws client.net.sf.saxon.ce.trans.XPathException * if any dynamic error occurs evaluating the * expression */ public SequenceIterator iterate(XPathContext context) throws XPathException { RegexIterator iter = getRegexIterator(context); XPathContextMajor c2 = context.newContext(); c2.setCurrentIterator(iter); c2.setCurrentRegexIterator(iter); AnalyzeMappingFunction fn = new AnalyzeMappingFunction(iter, c2); return new ContextMappingIterator(fn, c2); } /** * Mapping function that maps the sequence of matching/non-matching strings to the * sequence delivered by applying the matching-substring and non-matching-substring * expressions respectively to each such string */ private class AnalyzeMappingFunction implements ContextMappingFunction { private RegexIterator base; private XPathContext c2; public AnalyzeMappingFunction(RegexIterator base, XPathContext c2) { this.base = base; this.c2 = c2; } /** * Map one item to a sequence. * * @param context The processing context. Some mapping functions use this because they require * context information. Some mapping functions modify the context by maintaining the context item * and position. In other cases, the context may be 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 { if (base.isMatching()) { if (matching != null) { return matching.iterate(c2); } } else { if (nonMatching != null) { return nonMatching.iterate(c2); } } return EmptyIterator.getInstance(); } } } // 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.