0

I am trying to add a new element to my XML file data as below:

Orignal:

<storage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="1.0" ssd-storage="hybrid">
     <server port="6890" />
     <data-store name="mystore1" limit="700">
          <data-dir>${SSD_STORE_BASE}/myFolder1</data-dir>
     </data-store>
     <data-store name="mystore2" limit="700">
          <data-dir>${HDD_STORE_BASE}/myFolder2</data-dir>
     </data-store>
     <data-store name="mystore3" limit="700">
          <data-dir>${SSD_STORE_BASE}/myFolder3</data-dir>
     </data-store>
     .... many such data-stores for both HDD and SSD
</storage>

Expected

<storage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="1.0" ssd-storage="hybrid">
     <server port="6890" />
     <data-store name="mystore1" limit="700">
          <data-dir>${SSD_STORE_BASE1}/myFolder1</data-dir>
          <data-dir>${SSD_STORE_BASE2}/myFolder1</data-dir>
     </data-store>
     <data-store name="mystore2" limit="700">
          <data-dir>${HDD_STORE_BASE1}/myFolder2</data-dir>
          <data-dir>${HDD_STORE_BASE2}/myFolder2</data-dir>
     </data-store>
     <data-store name="mystore3" limit="700">
          <data-dir>${SSD_STORE_BASE1}/myFolder3</data-dir>
          <data-dir>${SSD_STORE_BASE2}/myFolder3</data-dir>
     </data-store>
     .... many such data-stores for both HDD and SSD
</storage>

I want to pass the replacement parameters for SSD_STORE_BASE and HDD_STORE_BASE, and the XSLT will need to append a copy of "data-dir" element with a replaced value of the text in the element. How do I do this? If I can use xsltproc to do this, that would be perfect.

1 Answer 1

1

You can achieve that with the following XSLT-1.0 stylesheet which can be processed by xsltproc with parameters. Set the parameters with

xsltproc --stringparam replacementSSD "newSSDValue" --stringparam replacementHDD "newHDDValue" transform.xslt input.xml

This can be transform.xslt:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:param name="replacementSSD" select="'SSD_STORE_BASE'" />
  <xsl:param name="replacementHDD" select="'HDD_STORE_BASE'" />
    
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template> 
    
    <xsl:template match="data-dir[contains(.,'SSD_STORE_BASE')]">
      <xsl:copy>
        <xsl:value-of select="concat(substring-before(.,'SSD_STORE_BASE'),$replacementSSD,'1',substring-after(.,'SSD_STORE_BASE'))" />
      </xsl:copy>
      <xsl:text>&#xa;</xsl:text>
      <xsl:copy>
        <xsl:value-of select="concat(substring-before(.,'SSD_STORE_BASE'),$replacementSSD,'2',substring-after(.,'SSD_STORE_BASE'))" />
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="data-dir[contains(.,'HDD_STORE_BASE')]">
      <xsl:copy>
        <xsl:value-of select="concat(substring-before(.,'HDD_STORE_BASE'),$replacementHDD,'1',substring-after(.,'HDD_STORE_BASE'))" />
      </xsl:copy>
      <xsl:copy>
        <xsl:value-of select="concat(substring-before(.,'SSD_STORE_BASE'),$replacementHDD,'2',substring-after(.,'HDD_STORE_BASE'))" />
      </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

This stylesheet could be optimized by merging the two templates, but I left them separately, because they may contain different functionality.


An updated answer reflecting the new requirements:

Counting is a bit complicated in XSLT-1.0, so I created a data-island that takes care of that - named cnt:counter. In this example it counts up to three. Then, in the code, it iterates over this element to count up to three - the main string is preserved in a variable.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:cache="geode.apache.org/schema/cache" xmlns:cnt="http://count.com">
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:param name="replacementSSD" select="'SSD_STORE_BASE'" />
  <xsl:param name="replacementHDD" select="'HDD_STORE_BASE'" />
    
  <cnt:counter>
    <cnt:cnt>1</cnt:cnt>
    <cnt:cnt>2</cnt:cnt>
    <cnt:cnt>3</cnt:cnt>
  </cnt:counter>
    
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template> 
    
    <xsl:template match="cache:data-dir[contains(.,'SSD_STORE_BASE')]">
      <xsl:variable name="curStr" select="." />
      <xsl:for-each select="document('')/xsl:stylesheet/cnt:counter/cnt:cnt">
        <xsl:element name="data-dir" namespace="geode.apache.org/schema/cache">
            <xsl:value-of select="concat(substring-before($curStr,'SSD_STORE_BASE'),$replacementSSD,.,substring-after($curStr,'SSD_STORE_BASE'))" />
        </xsl:element>
        <xsl:text>&#xa;</xsl:text>
      </xsl:for-each>
    </xsl:template>
    
    <xsl:template match="cache:data-dir[contains(.,'HDD_STORE_BASE')]">
      <xsl:variable name="curStr" select="." />
      <xsl:for-each select="document('')/xsl:stylesheet/cnt:counter/cnt:cnt">
        <xsl:element name="data-dir" namespace="geode.apache.org/schema/cache">
            <xsl:value-of select="concat(substring-before($curStr,'HDD_STORE_BASE'),$replacementHDD,.,substring-after($curStr,'HDD_STORE_BASE'))" />
        </xsl:element>
        <xsl:text>&#xa;</xsl:text>
      </xsl:for-each>
    </xsl:template>
    
</xsl:stylesheet>

Its output is:

<storage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="geode.apache.org/schema/cache" version="1.0" ssd-storage="hybrid">
     <server port="6890"/>
     <data-store name="mystore1" limit="700">
          <data-dir>${SSD_STORE_BASE1}/myFolder1</data-dir>
          <data-dir>${SSD_STORE_BASE2}/myFolder1</data-dir>
          <data-dir>${SSD_STORE_BASE3}/myFolder1</data-dir>
     </data-store>
     <data-store name="mystore2" limit="700">
          <data-dir>${HDD_STORE_BASE1}/myFolder2</data-dir>
          <data-dir>${HDD_STORE_BASE2}/myFolder2</data-dir>
          <data-dir>${HDD_STORE_BASE3}/myFolder2</data-dir>
     </data-store>
     <data-store name="mystore3" limit="700">
          <data-dir>${SSD_STORE_BASE1}/myFolder3</data-dir>
          <data-dir>${SSD_STORE_BASE2}/myFolder3</data-dir>
          <data-dir>${SSD_STORE_BASE3}/myFolder3</data-dir>
     </data-store>
     .... many such data-stores for both HDD and SSD
</storage>

This solution does handle the newly introduced namespace and the counting in XSLT-1.0.

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

2 Comments

That worked perfectly. However, I forgot to include the "xmlns" in the original XML, and strangely when I add that and re-run, it fails to do the transformation. It doesn't give any error, but no changes happens, simply the original XML is printed. <storage xmlns:xsi="w3.org/2001/XMLSchema-instance" xmlns="geode.apache.org/schema/cache" version="1.0" ssd-storage="hybrid"> Also, is it possible to repeat the added element "n" number of times with number incrementing from 1 to n time? "n" can be passed as an input to the xsltproc.
I updated my answer. Now it can handle the namespace and the counting.

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.