Home > Blockchain >  XSLT select attributes from another node
XSLT select attributes from another node

Time:05-05

This is my XML file :

<root>
    <headers>
        <header value="type1" />
        <header value="type3" />
        <header value="type2" />
    </headers>
    <data type1="data1_1" type2="data1_2" type3="data1_3" />
    <data type1="data2_1" type2="data2_2" type3="data2_3" />
</root>

And I want to generate CSV with only headers listed in headers section. This is my xslt file :

<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" encoding="unicode" />
<xsl:template match="root">
     <xsl:apply-templates/>
</xsl:template>


<xsl:template match="headers">
    <xsl:apply-templates/><xsl:text>&#xa;</xsl:text>
</xsl:template>


<xsl:template name="separator">
    <xsl:text>&#x09;</xsl:text>
</xsl:template>

<!-- display first line header work ok -->
<xsl:template match="header">
    <xsl:value-of select="@value" />
    <xsl:if test="position() != last()">
      <xsl:call-template name="separator" />
   </xsl:if>
</xsl:template>


<xsl:key name="mykey" match="header" use="@value" />

<!-- For each data parameters check if exist in header and display with the good order ! NOT WORK -->
<xsl:template match="data">
    <xsl:for-each select="@*">
        <xsl:if test="key('mykey', name())">
            <xsl:value-of select="." />
            <xsl:if test="position() != last()">
                <xsl:call-template name="separator" />
            </xsl:if>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

I want this output format (CSV):

type1 type3 type2
data1_1 data1_3 data1_2
data2_1 data2_3 data2_2

But my output is ( data type2 is not under correct header ):

type1 type3 type2
data1_1 data1_2 data1_3
data2_1 data2_2 data2_3

CodePudding user response:

I would do simply:

XSLT 1.0

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

<xsl:template match="/root">
    <xsl:variable name="headers" select="headers/header" />
    <!-- header -->
    <xsl:for-each select="$headers">
        <xsl:value-of select="@value"/>
        <xsl:if test="position()!=last()">
            <xsl:text>&#9;</xsl:text>
        </xsl:if>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
    <!-- data -->
    <xsl:for-each select="data">
        <xsl:variable name="data" select="." />
        <xsl:for-each select="$headers">
            <xsl:value-of select="$data/@*[name()=current()/@value]"/>
            <xsl:if test="position()!=last()">
                <xsl:text>&#9;</xsl:text>
            </xsl:if>
        </xsl:for-each> 
        <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Alternatively (and perhaps a bit more efficiently), you could do:

XSLT 1.0

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

<xsl:key name="data-value" match="data/@*" use="concat(name(), '|', generate-id(..))" />

<xsl:template match="/root">
    <xsl:variable name="headers" select="headers/header" />
    <!-- header -->
    <xsl:for-each select="$headers">
        <xsl:value-of select="@value"/>
        <xsl:if test="position()!=last()">
            <xsl:text>&#9;</xsl:text>
        </xsl:if>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
    <!-- data -->
    <xsl:for-each select="data">
        <xsl:variable name="data-id" select="generate-id()" />
        <xsl:for-each select="$headers">
            <xsl:value-of select="key('data-value', concat(@value, '|', $data-id))"/>
            <xsl:if test="position()!=last()">
                <xsl:text>&#9;</xsl:text>
            </xsl:if>
        </xsl:for-each> 
        <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

BTW, the result is a tab-separated file, not CSV.

CodePudding user response:

Attributes have no set order. Elements do. So let's use the elements to provide your ordering:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" encoding="unicode" />
<xsl:template match="Results">
     <xsl:apply-templates/>
</xsl:template>

<xsl:template match="headers">
    <xsl:apply-templates/><xsl:text>&#xa;</xsl:text>
</xsl:template>

<xsl:template name="separator">
    <xsl:text>&#x09;</xsl:text>
</xsl:template>

<!-- display first line header work ok -->
<xsl:template match="header">
    <xsl:value-of select="@value" />
    <xsl:if test="position() != last()">
      <xsl:call-template name="separator" />
   </xsl:if>
</xsl:template>

<!-- Use the order of the headers to choose the order of the attributes. -->
<xsl:template match="data">
    <xsl:variable name="data" select="."/>
    <xsl:for-each select="/root/headers/header">
        <xsl:value-of select="$data/@*[name()=current()/@value]"/>        
            <xsl:if test="position() != last()">
                <xsl:call-template name="separator" />
            </xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Output:

    type1   
    type3   
    type2   


data1_1 data1_3 data1_2
data2_1 data2_3 data2_2

The headers are on separate lines because I didn't change that part of your code.

CodePudding user response:

  1. Create a template for text() nodes to get rid of all the text between elements (new lines, indents)
  2. Put new lines between rows explicitly

Here is your xslt with minor fixes that does what you want:

<?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" indent="no" encoding="unicode" />
    
    <xsl:template match="root">
        <xsl:apply-templates/>
    </xsl:template>
            
    <xsl:template match="headers">
        <xsl:apply-templates/><xsl:text>&#xa;</xsl:text>
    </xsl:template>
    
    <xsl:template match="text()"></xsl:template>
    
    <xsl:template name="separator">
        <xsl:text> </xsl:text>
    </xsl:template>
    
    <!-- display first line header work ok -->
    <xsl:template match="header">
        <xsl:value-of select="@value" />
        <xsl:if test="position() != last()">
            <xsl:call-template name="separator" />
        </xsl:if>
    </xsl:template>
    
    
    <xsl:key name="mykey" match="header" use="@value" />
    
    <!-- For each data parameters check if exist in header and display with the good order ! NOT WORK -->
    <xsl:template match="data">
        <xsl:for-each select="@*">
            <xsl:if test="key('mykey', name())">
                <xsl:value-of select="." />
                <xsl:choose>
                    <xsl:when test="position() != last()">
                        <xsl:call-template name="separator" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:text>
</xsl:text>
                    </xsl:otherwise>
                </xsl:choose>
                
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
    
</xsl:stylesheet>
  • Related