smeeb smeeb - 3 months ago 48
Groovy Question

Groovy string replaceAll not working as expected

I have a string of valid XML (containing XML tags, attributes, comments, whitespaces, etc.) that contains special tokens of the form:


%(*)


For example,
%(testLibDir)
, or
%(ivySettingsFile)
, etc.

I am trying to replace all instances of
%(*)
with
${*}
. So for example, if
%(testLibDir)
exists within my XML string (regardless of how many occurrences), then I want it converted into
${testLibDir}
, etc.

My best attempt thus far:

static void main(String[] args) {
// In reality the XML string will be generated dynamically at runtime.
String xml = """
<project name="some-name" default="dist" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant">

<!-- specify properties in build.properties -->
<property file="build.properties" />

<taskdef name="ivy-configure" classname="org.apache.ivy.ant.IvyConfigure" />
<taskdef name="ivy-resolve" classname="org.apache.ivy.ant.IvyResolve" />
<taskdef name="ivy-retrieve" classname="org.apache.ivy.ant.IvyRetrieve" />
<taskdef name="ivy-cleancache" classname="org.apache.ivy.ant.IvyCleanCache" />

<!-- Identify the Ivy settings file to use -->
<ivy:settings file="%(ivySettingsFile)" />

<target name="test-resolve">
<delete>
<fileset dir="%(testLibDir)" includes="*.jar" />
</delete>
<ivy:resolve file="test-ivy.xml" conf="compile" />
<ivy:retrieve pattern="%(testLibDir)/[artifact]-[type]-[revision].[ext]" conf="compile" />
</target>
"""
println "Final XML is:\n${xml.replaceAll('%(', '\${').replaceAll(')', '}')}"
}


The
println
above produces the following stacktrace:

log4j:ERROR Could not find value for key log4j.appender.stdout.layout
log4j:WARN No such property [file] in org.apache.log4j.ConsoleAppender.
Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed group near index 2
%(
^
at java.util.regex.Pattern.error(Pattern.java:1955)
at java.util.regex.Pattern.accept(Pattern.java:1813)
at java.util.regex.Pattern.group0(Pattern.java:2908)
(rest of stack trace omitted for brevity)


Any ideas where I'm going awry and what I can do to fix my string replacement?

Answer

You need to use a regex like:

/%\(([^()]*)\)/

to match

  • % - a percentage sing
  • \( - a literal (
  • ([^()]*) - Group 1 capturing 0+ chars other than ( and )
  • \) - a literal )

Note that the pattern itself is better defined with a slashy strings, and the literal $ in the replacement pattern should be escaped with a literal \ to be treated as a literal dollar symbol. $1 is a backreference to the value captured by Group 1.

Groovy demo:

println xml.replaceAll(/%\(([^()]*)\)/, '\\${$1}')
Comments