jcools
jcools

Reputation: 69

Using xslt append new element in xml data

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.

Upvotes: 0

Views: 192

Answers (1)

zx485
zx485

Reputation: 29022

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.

Upvotes: 1

Related Questions