0
votes

I'm trying to update all nodes with the same pattern using xmlstarlet. Given the following xml

<root>
  <application>
    <provider name="alpha" value="my.corp.lion" />
    <provider name="beta" value="my.corp.tiger" />
    <provider name="gamma" value="my.corp.monkey" />
  </application>
</root>

I can currently update each node as follows

oldCorp="my.corp"
newCorp="new.my.corp"
myNode="/root/application/provider[@name='alpha']/@value"
oldValue=$(xml sel -t -v ${myNode} MyXml.xml)
newValue=${oldValue//$oldCorp/$newCorp}
xml ed --inplace -u ${myNode} -v "${newValue}" MyXml.xml

# results in provider.alpha being new.my.corp.lion

What I'd like to be able to do however is foreach ALL provider nodes and update the final xml result to be

<root>
  <application>
    <provider name="alpha" value="new.my.corp.lion" />
    <provider name="beta" value="new.my.corp.tiger" />
    <provider name="gamma" value="new.my.corp.monkey" />
  </application>
</root>

Is there a way to do a foreach over the providers and replace all my.corp instances with new.my.corp?

1

1 Answers

1
votes

In general, use the -x option of xmlstarlet like this:

xmlstarlet ed --inplace -u "/root/application/provider/@value" -x "concat('new.my.corp',substring-after(.,'my.corp'))" input.xml

In your special case, change the code to

oldCorp="my.corp"
newCorp="new.my.corp"
myNode="/root/application/provider/@value"
xml ed --inplace -u "${myNode}" -x "concat('${newCorp}',substring-after(.,'${oldCorp}'))" MyXml.xml

The output is as desired:

<?xml version="1.0"?>
<root>
  <application>
    <provider name="alpha" value="new.my.corp.lion"/>
    <provider name="beta" value="new.my.corp.tiger"/>
    <provider name="gamma" value="new.my.corp.monkey"/>
  </application>
</root>

This code replaces all attribute values specified by the myNode XPath with the value constructed in the -x argument of xmlstarlet.

AFAIK xmlstarlet does not support RegEx'es, so you have to create the replacement expressions with XPath-1.0 functions like substring-after(...), substring(...) and so on.


If you need RegEx'es for your replacement, you have to use XPath-2.0 functions which are part of XSLT-2.0. Then you can use an XSLT-2.0 stylesheet like the following which achieves the same thing, but with RegEx'es:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
  <xsl:param name="oldCorp" select="'my\.corp'" />
  <xsl:param name="newCorp" select="'new.my.corp'" />

  <!-- Identity template -->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*" />
    </xsl:copy>
  </xsl:template>  

  <xsl:template match="provider">
    <xsl:copy>
      <xsl:copy-of select="@*" />
      <xsl:attribute name="value">
        <xsl:value-of select="replace(@value,$oldCorp,$newCorp)" />
      </xsl:attribute>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

You can pass the parameters as strings to the XSLT-2.0 processor. Both parameters are initialized with your default values.