0

I'm trying to change the value of an specific XML field using XSLT 2.0 if the value ends with "i". The change consists of adding a "0" (zero) before the value.

XML field of interst would be '/message/object/struct/substruct2/ss2field2'. Change should occur for every 'ssfield2' ending with "i", in every 'object'.

The source XML is as follows:

<message>
    <object>
        <meta>
            <field>text</field>
        </meta>
        <struct>
            <sfield1>text</sfield1>
            <sfield2>text</sfield2>
            <substruct1>
                <ss1field>text</ss1field>
            </substruct1>
            <substruct2>
                <ss2field1>text</ss2field1>
                <ss2field2>0001</ss2field2>
            </substruct2>
            <substruct2>
                <ss2field1>text</ss2field1>
                <ss2field2>002i</ss2field2>
            </substruct2>
            <substruct2>
                <ss2field1>text</ss2field1>
                <ss2field2>0003</ss2field2>
            </substruct2>
        </struct>
    </object>
    <object>
        <meta>
            <field>text</field>
        </meta>
        <struct>
            <sfield1>text</sfield1>
            <sfield2>text</sfield2>
            <substruct1>
                <ss1field>text</ss1field>
            </substruct1>
            <substruct2>
                <ss2field1>text</ss2field1>
                <ss2field2>004i</ss2field2>
            </substruct2>
            <substruct2>
                <ss2field1>text</ss2field1>
                <ss2field2>0005</ss2field2>
            </substruct2>
        </struct>
    </object>
</message>

Based in this other post: Xslt change element value based on when condition rule with reference to another element

I tried to build my own stylesheet:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" omit-xml-declaration="yes"/>
    <xsl:variable name="condition">
        <xsl:for-each select="//substruct2">
            <xsl:choose>
                <xsl:when test="ends-with(ss2field2,'i')">
                    <xsl:value-of select="concat('0',ss2field2)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="ss2field2"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:variable>
    <xsl:template name="value-to-replace">
        <xsl:param name="param.str"/>
        <xsl:param name="param.target"/>
        <xsl:param name="param.replacement"/>
        <xsl:choose>
            <xsl:when test="contains($param.str, $param.target)">
                <xsl:value-of select="concat(substring-before($param.str, $param.target), $param.replacement, substring-after($param.str, $param.target))"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$param.str"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="ss2field2[parent::substruct2]">
        <xsl:element name="{name()}">
            <xsl:call-template name="value-to-replace">
                <xsl:with-param name="param.str" select="."/>
                <xsl:with-param name="param.target" select="//substruct2/ss2field2"/>
                <xsl:with-param name="param.replacement" select="$condition"/>
            </xsl:call-template>
        </xsl:element>
    </xsl:template>
    <!--copy all nodes-->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

But it does not work, I'm getting the following error: A sequence of more than one item is not allowed as the second argument of contains() ("0001", "002i", ...)

Any help would be very much appreciated.

1 Answers1

0

Nevermind, I was over complicating the solution, the following XSLT did the trick:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="//ss2field2">
        <xsl:copy>
            <xsl:choose>
                <xsl:when test="ends-with(.,'i')">
                    <xsl:value-of select="concat('0', .)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

I have now another problem but I will save it for another question.

  • For a `match` pattern in XSLT, a leading `//` is never needed so you could shorten `match="//ss2field2"` to `match="ss2field2"`. Also, your `xsl:choose` for the `xsl:otherwise` could be solved by the identity template you already have so consider moving the condition check to a match predicate ``. That simplifies the code. – Martin Honnen Jul 03 '23 at 12:36