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.
<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> </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> </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 </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> </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 </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> </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.