1

Assuming I have a pom.xml containing a parent dependency like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>some-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
</project>

Is there a way I can replace the 1.0.0-SNAPSHOT version programmatically, maybe via a script?

My use case:

  • I have lots of pom.xml files that I need to change the parent version of
  • I'm using git-xargs to make changes across multiple repos, so I can apply a bash script to each pom.xml to change it.

For example, I can do the following, but this will update all occurrences of 1.0.0-SNAPSHOT in the pom.xml, and I want to limit it to just the some-parent artifact that has a version 1.0.0-SNAPSHOT:

git-xargs \
  --branch-name test-branch \
  --github-org <your-github-org> \
  --commit-message "Update pom.xml" \
  sed -i 's/1.0.0-SNAPSHOT/2.0.1/g' pom.xml

As per the git-xargs docs, I can use any type of script to process the pom.xml, bash, python, ruby etc: https://github.com/gruntwork-io/git-xargs#how-to-supply-commands-or-scripts-to-run

UPDATE:

The following xmlstarlet approach works up to a point:

if [[ $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:artifactId' pom.xml) == "some-parent" && $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:version' pom.xml) == "1.0.0-SNAPSHOT" ]]; then xmlstarlet edit -L -N my=http://maven.apache.org/POM/4.0.0 --update '//my:project/my:parent/my:version' --value '2.0.6' pom.xml; fi

xmlstarlet is correctly updating the xml element I want, but its also reordering the xsi:schemaLocation, xmlns and xmlns:xsi at the top of my file.

It's updating it from:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

to:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

which is no good for me.

1

4 Answers 4

3

You could use xq.

Just setting a certain element to a new value would be as simple as

xq -x '.project.parent.version = "2.0.1"' pom.xml

Setting it on condition would be

xq -x '(.project.parent | select(
  .artifactId == "some-parent" and .version == "1.0.0-SNAPSHOT"
)).version = "2.0.1"' pom.xml

Fed with the sample data, both would produce

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example</groupId>
    <artifactId>some-parent</artifactId>
    <version>2.0.1</version>
  </parent>
</project>

As you can see, the order of the attributes stays the same, however the indentation is different, especially your in-tag line breaks were swallowed. Also, the XML declaration (first line) is missing, as would comments, if present in the input. Maybe this isn't viable for you either, but consider

  • editing the logical structure (rather than its textual representation) is less error-prone,
  • all of these issues (except for the missing comments, of course) could easily be remedied using one of many XML pretty-printers available for the command-line. This is a (non-exhaustive) list of where to go from here.

For instance, let's go with tidy.

With this configuration

xq -x '…' pom.xml |
tidy -xml -iq -utf8 --indent-with-tabs yes --indent-spaces 4 --tab-size 4 \
  --indent-attributes yes --add-xml-decl yes

you would get as far as

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>some-parent</artifactId>
        <version>2.0.1</version>
    </parent>
</project>

For what I can see, this is still lacking the encoding="UTF-8" in the top-line declaration (tidy refuses to print obvious declarations), and the indentation of the attributes is still off by 1 character. If this is still an issue, try playing around with the other pretty-printers out there. Chances are you'll find the right one for the perfect finishing.

Sign up to request clarification or add additional context in comments.

Comments

2
+150

You should avoid working with xml as plain text files, but if you have no other options you may give it a try. sed ranges (/starting-regex/,/ending-regex/{commands}) allows to restrict your replacement within specific context. I.e. this oneliner:

sed -e '/<parent/,/\/parent/{/artifactId>some-parent/,/parent/{s/1.0.0-SNAPSHOT/2.0.0-SNAPSHOT/g}}'

will replace only version strings, which are between <parent and /parent strings, and between some-parent and parent lines, thus should only match artifacts some-parent. Obviously, there are multiple cases when it will fail, but for simple files this could work. Your ranges may be further nested.

Comments

2

With :

While you can use --if and --else for xmlstarlet sel (see https://stackoverflow.com/a/35671038/2703456 for example), I don't think you can use them for xmlstarlet ed. I hardly ever use xmlstarlet, so I'm not entirely sure.
That means you'll have to do it in Bash...

$ if [[ $(xmlstarlet sel -t -v 'parent/version' pom.xml) == "1.0.0-SNAPSHOT" ]]
  then xmlstarlet ed -O -u 'parent/version' -v 2.0.1 pom.xml
  fi
<parent>
  <groupId>com.example</groupId>
  <artifactId>some-parent</artifactId>
  <version>2.0.1</version>
</parent>

With :

$ xidel -s pom.xml -e '
  if (parent/version = "1.0.0-SNAPSHOT")
  then x:replace-nodes(parent/version/text(),"2.0.1")
  else .
' --output-node-format=xml
<parent>
    <groupId>com.example</groupId>
    <artifactId>some-parent</artifactId>
    <version>2.0.1</version>
</parent>

If you want the output to include an XML declaration, for xmlstarlet remove the -O option and for xidel use --output-format=xml instead.

4 Comments

Thanks @Reino Is there a way to do it so that it will only replace the version if the current version is 1.0.0-SNAPSHOT, e.g. if the current version was 2.0.0-SNAPSHOT, I don't want the version replaced. Thanks
@rmf See updated answer. Btw, if you have to edit A LOT of these XML-files, then you might be interested in xidel's "EXPath File Module". With just 1 xidel call you can then process all of them at once.
Not like that. It needs / to not replace it, () deletes everything.
@BeniBela Sorry. My mistake. Corrected.
0

This works, using a combination of xmlstarlet and mvn:

#! /usr/bin/env bash
if [[ $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:artifactId' pom.xml) == "some-parent" && \
      $(xmlstarlet sel -N my=http://maven.apache.org/POM/4.0.0 -t -v '//my:project/my:parent/my:version' pom.xml) == "4.0.0-SNAPSHOT" ]]; then 
    mvn versions:update-parent -DparentVersion=[4.0.0-661]
    mvn versions:commit
fi

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.