net.sf.saxon.functions.Replace Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of saxon-he Show documentation
Show all versions of saxon-he Show documentation
An OSGi bundle for Saxon-HE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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;
}
}