Search code examples
xmlxslttransformationclob

XML transformation - duplicate node and concat string to existing tag values


Im new to xsltproc and want to transform this XML:

<services>
    <event name="Something.zip">
        <enabled>true</enabled>
        <bindings>
            <binding name="Example">
                <machine>example-name</machine>
                <product>
                    <type>Shared</type>
                    <version>1.0</version>
                    <location>/path/to/something</location>
                </product>
            </binding>
        </bindings>
    </event>
</services>

To this one:

<services>
    <event name="Something.zip">
        <enabled>true</enabled>
        <bindings>
            <binding name="Example">
                <machine>example-name</machine>
                <product>
                    <type>Shared</type>
                    <version>1.0</version>
                    <location>/path/to/something</location>
                </product>
            </binding>
            <binding name="Example-1">
                <machine>example-name-test</machine>
                <product>
                    <type>Shared</type>
                    <version>1.0</version>
                    <location>/path/to/something</location>
                </product>
            </binding>
        </bindings>
    </event>
</services>

Those are the points that I should follow:

  1. All the original .xml file content should be preserved.
  2. Inside bindings, I need to duplicate the current binding and take its name and add "-1" to it. Also, I need to add "-test" to the value inside the "machine" tag.
  3. The name of each binding can vary, thus, I cannot rely on the string "Example" as constant input. Same is true for the value inside "machine" tag.
  4. Suppose there is only one tag in each .xml file, and there will always be 1 binding inside it.

The insert.xsl that I wrote:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://some/xml/namespace" xmlns:tc="http://some/xml/namespace" exclude-result-prefixes="tc">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="tc:binding">
        <xsl:copy-of select="."/>
        <binding name="{@name}-1">
            <xsl:copy-of select="*"/>
        </binding>
    </xsl:template>
</xsl:stylesheet>

With help from y.arazim I have duplicated the binding inside the bindings and change the name attribute as suggested. Yet, cant solve how to change the in the new Example-1 binding block.

P.S, if I have tag value with blank spaces, will the xsltproc remove those whitespaces and leave a closed tag instead? If yes, is there an option to avoid this behavior?

Working Stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://some/xml/namespace" xmlns:tc="http://some/xml/namespace" exclude-result-prefixes="tc">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="tc:binding">
        <xsl:copy-of select="."/>
        <binding name="{@name}-1">
            <machine>
                <xsl:value-of select="concat(tc:machine, '-test')"/>
            </machine>
            <xsl:copy-of select="*[not(self::tc:machine)]"/>
        </binding>
    </xsl:template>
</xsl:stylesheet>

After long hauling, the final solution(based on y.arazim answer) was to retrieve the xml elements from the referred namespace by adding the prefix before the element.


Solution

  • For some reason it does nothing.

    That's not true. It does something: it throws an error:

    Cannot add attributes to an element if children have been already added to the element.
    

    To get the result you show, try something like:

    <xsl:template match="binding">
        <xsl:copy-of select="."/>
        <binding name="{@name}-1">
            <machine>
                <xsl:value-of select="concat(machine, '-test')"/>
            </machine>
            <xsl:copy-of select="product"/>
        </binding>
    </xsl:template>
    

    Do note that your stylesheet declares a default namespace xmlns="http://some/xml/namespace". I don't know whether you really want this or if that's just an attempt at "voodoo programming", but it will change the result significantly.


    Re your question about spaces, see: https://www.w3.org/TR/1999/REC-xslt-19991116/#strip


    Added: you can see it working here.