Segmented
Segmented

Reputation: 2044

Traversing a nested XML structure with XSLT template

I am new to XSLT. I have looked through the different solutions in this forum but my problem is a little different. Suppose we have a xml:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Transaction>
    <operation>DEBIT</operation>
    <autoRegistration>true</autoRegistration>
    <allowRecurrence>true</allowRecurrence>
    <network>VISA</network>
    <source>RANDOM</source>
    <origin>
        <merchant>ABC</merchant>
        <transactionId>1234</transactionId>
        <channel>SINGLE</channel>
        <country>DE</country>
    </origin>
    <customer>
        <number>338317</number>
        <eMail>[email protected]</eMail>
        <dateOfBirth>1975-06-15</dateOfBirth>
        <contact>
            <eMail>[email protected]</eMail>
            <phoneNumbers></phoneNumbers>
        </contact>
        <clientInfo>
            <ip>123.111.123.123</ip>
            <userAgent>Mozilla/5.0 (Windows NT 6.2; WOW64)</userAgent>
            <acceptHeader>*/*</acceptHeader>
        </clientInfo>
    </customer>
</Transaction>

Suppose I need to transform this to CSV using XSLT. I need my CSV as follows:

operation,allowRecurrence,source,merchant
DEBIT,true,RANDOM,ABC

The real world problem is more complex with more nested xml elements. How should I design my XSLT for such a problem such that it handles multiple levels of nested XML elements and prepares a CSV for me. So far I have managed to put together:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>

    <xsl:variable name="delimiter" select="','"/>

    <!-- define an array containing the fields we are interested in -->
    <xsl:variable name="fieldArray">
        <field>operation</field>
        <field>allowRecurrence</field>
        <field>source</field>
        <field>merchant</field>
    </xsl:variable>
    <xsl:param name="fields" select="document('')/*/xsl:variable[@name='fieldArray']/*"/>

    <xsl:template match="/">
        <xsl:variable name="currNode" select="."/>
        <!-- output the header row -->
        <xsl:for-each select="$fields">
            <xsl:if test="position() != 1">
                <xsl:value-of select="$delimiter"/>
            </xsl:if>
            <xsl:value-of select="."/>
        </xsl:for-each>

        <!-- output newline -->
    <xsl:text>
</xsl:text>
        <xsl:for-each select="$fields" >
            <xsl:if test="position() != 1">
                <xsl:value-of select="$delimiter"/>
            </xsl:if>
            <xsl:value-of select="$currNode/*/*[name() = current()]"/>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

I am stuck at the logic <xsl:value-of select="$currNode/*/*[name() = current()]"/>... How do I vary the level of XML element nesting in this loop.

Upvotes: 0

Views: 649

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 116982

XML documents are not "arbitrary"; they follow a pre-defined schema. Your best strategy is to tailor your XSLT to the expected schema. In your example, that would mean something like:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>

<xsl:template match="/Transaction">
    <xsl:text>operation,allowRecurrence,source,merchant,eMail&#10;</xsl:text>
    <xsl:value-of select="operation"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="allowRecurrence"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="source"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="origin/merchant"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="customer/eMail"/>
</xsl:template>

</xsl:stylesheet>

Your idea of traversing the document, looking for a node named 'xyx' would fail when there are multiple nodes with the same name. For example, your XML has two eMail nodes, and your method would never reach the one at customer/contact/eMail.

Upvotes: 1

Related Questions