Home > Blockchain >  Get comma separated string into each row of CSV using XSLT
Get comma separated string into each row of CSV using XSLT

Time:06-27

I would like to make a xslt template that gets comma separated string into each row in CSV.

My current xslt is as below for reference. I don't have any issue getting information from my Person class, but as for Item, i need to get from variable and the template for "Item" below is not correct. And I cannot use tokenize since my xslt version is 1.

Id, Name and Address can be getting it from Person class in C#. ItemValue is a separate variable. The number of person count and ItemValue count are tally. Even if the count are mismatched, ItemValue can be left as empty. As I cannot attached a new variable to exiting person class.

xml would probably look like this.

<PersonList>
  <Person>
    <ID>1</ID>
    <Name>Name1</Name>
    <Address>Add1</Address>
  </Person>
  <Person>
    <ID>2</ID>
    <Name>Name2</Name>
    <Address>Add2</Address>
  </Person>
  <Person>
    <ID>3</ID>
    <Name>Name3</Name>
    <Address>Add3</Address>
  </Person>
  <Person>
    <ID>4</ID>
    <Name>Name4</Name>
    <Address>Add4</Address>
  </Person>
<PersonList>

My expected csv and xml will look like as below.

enter image description here

   <PersonList>
      <Person>
        <ID>1</ID>
        <Name>Name1</Name>
        <Address>Add1</Address>
        <ItemValue>item1</ItemValue>
      </Person>
      <Person>
        <ID>2</ID>
        <Name>Name2</Name>
        <Address>Add2</Address>
        <ItemValue>item2</ItemValue>
      </Person>
      <Person>
        <ID>3</ID>
        <Name>Name3</Name>
        <Address>Add3</Address>
        <ItemValue>item3</ItemValue>
      </Person>
      <Person>
        <ID>4</ID>
        <Name>Name4</Name>
        <Address>Add4</Address>
        <ItemValue>item4</ItemValue>
      </Person>
    <PersonList>

Below is current xslt. I don't add any xml tag since my desired output is in csv format.

        <?xml version="1.0" encoding="utf-8"?>
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                        xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">   
            
            <xsl:param name="items">
                <xsl:text>item1,item2,item3,item4</xsl:text>
            </xsl:param>

            
            <xsl:template name="Header" match="/">
                <xsl:text>Id</xsl:text>
                <xsl:text>,</xsl:text>
                <xsl:text>Name</xsl:text>
                <xsl:text>,</xsl:text>
                <xsl:text>Address</xsl:text>
                <xsl:text>,</xsl:text>
                <xsl:text>ItemValue</xsl:text>
                <xsl:text>&#10;</xsl:text>
                <xsl:apply-templates select="//Person" />
            </xsl:template>
            
            <xsl:template match="Person">
                <xsl:value-of select="@Id" />
                <xsl:text>,</xsl:text>
                <xsl:value-of select="@Name" />
                <xsl:text>,</xsl:text>
                <xsl:value-of select="@Address" />
                <xsl:text>,</xsl:text>
                <xsl:apply-templates select="//Item">
                    <xsl:with-param name="Person" select="." />
                </xsl:apply-templates>
                <xsl:text>&#10;</xsl:text>
            </xsl:template>
            
            <xsl:template match="Item">
                <xsl:variable name="itemList" select="substring-before(concat($items, ','), ',')" />
                <xsl:for-each select="$itemList">
                   <item>
                       <xsl:value-of select="."/>
                   </item>
                </xsl:for-each>
            </xsl:template>
        </xsl:stylesheet>

CodePudding user response:

One way you could handle this is to adapt the solution I linked to in the comments as follows:

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:param name="items">item1,item2,item3,item4</xsl:param>

<xsl:template match="/PersonList">
    <!-- header -->
    <xsl:text>Id,Name,Adress,ItemValue&#10;</xsl:text>
    <!-- data -->
    <xsl:for-each select="Person">
        <xsl:value-of select="ID"/>
        <xsl:text>,</xsl:text>  
        <xsl:value-of select="Name"/>
        <xsl:text>,</xsl:text>  
        <xsl:value-of select="Address"/>
        <xsl:text>,</xsl:text>  
        <xsl:call-template name="get-Nth-value">
            <xsl:with-param name="list" select="$items"/>
            <xsl:with-param name="N" select="position()"/>
        </xsl:call-template>
        <xsl:text>&#10;</xsl:text>  
        <!-- recursive call -->
    </xsl:for-each>
</xsl:template>

<xsl:template name="get-Nth-value">
    <xsl:param name="list"/>
    <xsl:param name="N"/>
    <xsl:param name="delimiter" select="','"/>
    <xsl:choose>
        <xsl:when test="$N = 1">
            <xsl:value-of select="substring-before(concat($list, $delimiter), $delimiter)"/>
        </xsl:when>
        <xsl:when test="contains($list, $delimiter) and $N > 1">
            <!-- recursive call -->
            <xsl:call-template name="get-Nth-value">
                <xsl:with-param name="list" select="substring-after($list, $delimiter)"/>
                <xsl:with-param name="N" select="$N - 1"/>
                <xsl:with-param name="delimiter" select="$delimiter"/>
            </xsl:call-template>
        </xsl:when>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

When applied to your example input (corrected for well-formedness!), this will return:

Result

Id,Name,Adress,ItemValue
1,Name1,Add1,item1
2,Name2,Add2,item2
3,Name3,Add3,item3
4,Name4,Add4,item4

However, it might be better to reduce the number of required iterations by processing both the XML and the $items parameter in parallel:

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:param name="items">item1,item2,item3,item4</xsl:param>

<xsl:template match="/PersonList">
    <!-- header -->
    <xsl:text>Id,Name,Adress,ItemValue&#10;</xsl:text>
    <!-- data -->
    <xsl:call-template name="generate-rows">
        <xsl:with-param name="persons" select="Person"/>
        <xsl:with-param name="items" select="$items"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="generate-rows">
    <xsl:param name="persons"/>
    <xsl:param name="items"/>
    <xsl:param name="delimiter">,</xsl:param>
    <xsl:if test="$persons">
        <xsl:variable name="person" select="$persons[1]" />
        <!-- write to output -->
        <xsl:value-of select="$person/ID"/>
        <xsl:text>,</xsl:text>  
        <xsl:value-of select="$person/Name"/>
        <xsl:text>,</xsl:text>  
        <xsl:value-of select="$person/Address"/>
        <xsl:text>,</xsl:text>  
        <xsl:value-of select="substring-before(concat($items, $delimiter), $delimiter)"/>
        <xsl:text>&#10;</xsl:text>  
        <!-- recursive call -->
        <xsl:call-template name="generate-rows">
            <xsl:with-param name="persons" select="$persons[position() > 1]"/>
            <xsl:with-param name="items" select="substring-after($items, $delimiter)"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

Note that this assumes that the $items parameter value is supplied at runtime. If it can be hard-coded, then the solution could be much simpler.

And we still don't know what should happen when the number of items is not equal to the number of persons.

  • Related