Home > database >  Convert XML to CSV (with node path as header) using XSLT
Convert XML to CSV (with node path as header) using XSLT

Time:02-11

How can I convert the following XML to CSV using XSLT?

<tXML>
    <Header>
        <Source>XPTO</Source>
        <User_ID>127</User_ID>
        <Message_Type>Ship</Message_Type>
        <Company_ID>105</Company_ID>
        <Msg_Locale>English (United States)</Msg_Locale>
        <Version>2017</Version>
    </Header>
    <Message>
        <Ship>
            <ShipSummary>
                <ComName>XPTO 123</ComName>
                <FacName>6</FacName>
            </ShipSummary>
        </Ship>
    </Message>
</tXML>
tXML/Header/Source tXML/Header/User_ID tXML/Header/Message_Type tXML/Header/Company_ID tXML/Header/Msg_Locale tXML/Header/Version tXML/Message/Ship/ShipSummary/ComName tXML/Message/Ship/ShipSummary/FacName
XPTO 127 Ship 105 English (United States) 2017 XPTO 123 6

How can I get the "node path" for every value, and use that as the header?

CodePudding user response:

When I run the following XSLT with xsltproc, I get your desired output (with one exception, there's an extra blank column at the end).

Much thanks to @Daniel_Haley for their solution to the general problem of printing the node path (please go vote that answer up if you like this).

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" />
    <xsl:strip-space elements="*" />
    <xsl:variable name="delim">;</xsl:variable>

    <xsl:template match="/">
        <!-- Recurse document for header -->
        <xsl:copy>
            <xsl:apply-templates select="node()" mode="header"/>
        </xsl:copy>

        <!-- Linebreak after last column in header -->
        <xsl:text>&#xA;</xsl:text>
        
        <!-- Recurse document for values -->
        <xsl:copy>
            <xsl:apply-templates select="node()" />
        </xsl:copy>

        <!-- Linebreak after last column in row -->
        <xsl:text>&#xA;</xsl:text>
    </xsl:template>

    <xsl:template match="text()">
        <xsl:copy-of select="." />
        <xsl:value-of select="$delim"/>
    </xsl:template>

    <!-- https://stackoverflow.com/a/10112579/246801 -->
    <xsl:template match="text()" mode="header">
        <xsl:for-each select="ancestor::*">
            <xsl:choose>
                <!-- avoid beginning slash (at root) -->
                <xsl:when test="position() = 1">
                    <xsl:value-of select="local-name()" />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="concat('/',local-name())" />
                </xsl:otherwise>
            </xsl:choose>
            <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
                <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())]) 1,']')" />
            </xsl:if>
        </xsl:for-each>
        <xsl:value-of select="$delim"/>
        <!-- <xsl:apply-templates select="node()" /> -->
    </xsl:template>

</xsl:stylesheet>
tXML/Header/Source tXML/Header/User_ID tXML/Header/Message_Type tXML/Header/Company_ID tXML/Header/Msg_Locale tXML/Header/Version tXML/Message/Ship/ShipSummary/ComName tXML/Message/Ship/ShipSummary/FacName
XPTO 127 Ship 105 English (United States) 2017 XPTO 123 6
  • Related