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

net.sf.saxon.functions.Replace 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.functions;

import net.sf.saxon.Configuration;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.regex.RegularExpression;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.DecimalValue;
import net.sf.saxon.value.StringValue;

import java.util.regex.PatternSyntaxException;


/**
* This class implements the replace() function for replacing
* substrings that match a regular expression
*/

public class Replace extends SystemFunctionCall implements Callable {

    /*@Nullable*/ private RegularExpression regexp;
    private boolean allow30features = false;
    private boolean replacementChecked = false;

    /**
    * Simplify and validate.
    * This is a pure function so it can be simplified in advance if the arguments are known
     * @param visitor an expression visitor
     */

     /*@NotNull*/
     public Expression simplify(ExpressionVisitor visitor) throws XPathException {
        allow30features = DecimalValue.THREE.equals(visitor.getStaticContext().getXPathLanguageLevel());
        Expression e = simplifyArguments(visitor);
        if (e == this) {
            maybePrecompile(visitor);
        }
        return e;
    }

    private void maybePrecompile(ExpressionVisitor visitor) throws XPathException {
        // compile the regular expression once if possible
        if (regexp == null) {
            try {
                regexp = Matches.tryToCompile(argument, 1, 3, visitor.getStaticContext());
            } catch (XPathException err) {
                err.setLocator(this);
                throw err;
            }

            // check that it's not a pattern that matches ""
            if (regexp != null && regexp.matches("")) {
                XPathException err = new XPathException("The regular expression in replace() must not be one that matches a zero-length string");
                err.setErrorCode("FORX0003");
                err.setLocator(this);
                throw err;
            }
        }
    }


    /**
     * Perform optimisation of an expression and its subexpressions.
     * 

*

This method is called after all references to functions and variables have been resolved * to the declaration of the function or variable, and after all type checking has been done.

* * @param visitor an expression visitor * @param contextItemType the static type of "." at the point where this expression is invoked. * The parameter is set to null if it is known statically that the context item will be undefined. * If the type of the context item is not known statically, the argument is set to * {@link net.sf.saxon.type.Type#ITEM_TYPE} * @return the original expression, rewritten if appropriate to optimize execution * @throws net.sf.saxon.trans.XPathException * if an error is discovered during this phase * (typically a type error) */ /*@NotNull*/ public Expression optimize(/*@NotNull*/ ExpressionVisitor visitor, ExpressionVisitor.ContextItemType contextItemType) throws XPathException { Expression e = super.optimize(visitor, contextItemType); // try once again to compile the regular expression once if possible // (used when the regex has been identified as a constant as a result of earlier rewrites) if (e == this) { maybePrecompile(visitor); } if (argument[2] instanceof StringLiteral) { // Do early checking of the replacement expression if known statically String rep = ((StringLiteral)argument[2]).getStringValue(); String msg = checkReplacement(rep); replacementChecked = true; if (msg != null) { XPathException ex = new XPathException(msg, "FORX0004"); ex.setLocator(this); return new ErrorExpression(ex); } } return e; } /** * Get the compiled regular expression if available, otherwise return null * @return the compiled regex, or null */ public RegularExpression getCompiledRegularExpression() { return regexp; } /** * Evaluate the function in a string context */ public StringValue evaluateItem(XPathContext c) throws XPathException { return eval((StringValue)argument[0].evaluateItem(c), (StringValue)argument[1].evaluateItem(c), (StringValue)argument[2].evaluateItem(c), (argument.length==3 ? null : (StringValue)argument[3].evaluateItem(c)), c); } /** * Evaluate the expression * * * * @param context the dynamic evaluation context * @param arguments the values of the arguments, supplied as SequenceIterators * @return the result of the evaluation, in the form of a SequenceIterator * @throws net.sf.saxon.trans.XPathException * if a dynamic error occurs during the evaluation of the expression */ public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException { return eval((StringValue)arguments[0].head(), (StringValue)arguments[1].head(), (StringValue)arguments[2].head(), (arguments.length == 3 ? null : (StringValue)arguments[3].head()), context); } /** * Internal method that does the work * @param inputArg * @param regexArg * @param replaceArg * @param flagsArg * @param context * @return * @throws XPathException */ private StringValue eval(StringValue inputArg, StringValue regexArg, StringValue replaceArg, StringValue flagsArg, XPathContext context) throws XPathException { if (inputArg==null) { inputArg = StringValue.EMPTY_STRING; } CharSequence replacement = replaceArg.getStringValueCS(); if (!replacementChecked) { // if it is a string literal, the check was done at compile time String msg = checkReplacement(replacement); if (msg != null) { dynamicError(msg, "FORX0004", context); } } RegularExpression re = regexp; if (re == null) { CharSequence flags; if (flagsArg == null) { flags = ""; } else { flags = flagsArg.getStringValueCS(); } try { re = Configuration.getPlatform().compileRegularExpression( regexArg.getStringValueCS(), flags.toString(), (allow30features ? "XP30" : "XP20"), null); } catch (XPathException err) { XPathException de = new XPathException(err); de.setErrorCode("FORX0002"); de.setXPathContext(context); de.setLocator(this); throw de; } catch (PatternSyntaxException err) { XPathException de = new XPathException(err); de.setErrorCode("FORX0002"); de.setXPathContext(context); de.setLocator(this); throw de; } // check that it's not a pattern that matches "" if (re.matches("")) { dynamicError( "The regular expression in replace() must not be one that matches a zero-length string", "FORX0003", context); } } String input = inputArg.getStringValue(); CharSequence res = re.replace(input, replacement); return StringValue.makeStringValue(res); } /** * Check the contents of the replacement string * @param rep the replacement string * @return null if the string is OK, or an error message if not */ public static String checkReplacement(CharSequence rep) { for (int i=0; i '9') { return "Invalid replacement string in replace(): $ sign must be followed by digit 0-9"; } } else { return "Invalid replacement string in replace(): $ sign at end of string"; } } else if (c == '\\') { if (i+1 < rep.length()) { char next = rep.charAt(++i); if (next != '\\' && next != '$') { return "Invalid replacement string in replace(): \\ character must be followed by \\ or $"; } } else { return "Invalid replacement string in replace(): \\ character at end of string"; } } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy