Reputation: 69
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
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>
</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>
</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>
</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