XSL lookup from a properties (csv) file - xslt

I have an XML which needs to be formatted to a CSV. I have chosen XSL to do achieve this. In addition to the XML file, I have a Properties file which needs to be looked through for getting the values for the variables defined in the XML.
Can any one help me in how to do the lookups via an XSL using an external csv file?
CSV file after transformation:
Article.AclFlag|%field.Article.AclFlag.name|false|true|true|||||||||||master-data|%category.MasterData|Integer||0|||Enum.Acls|%enum.Acls.name|%enum.Acls.entry.0;%enum.Acls.entry.1;%enum.Acls.entry.2;%enum.Acls.entry.3;%enum.Acls.entry.4;%enum.Acls.entry.5;%enum.Acls.entry.6;|0;1;2;3;4;5;6;|0;1;2;3;4;5;6;|Article|%entity.Article.name|Product2G Variant
XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:key name="kEntity" match="entity-type" use="#identifier" />
<xsl:key name="kCategory" match="category" use="#identifier" />
<xsl:key name="kFieldID" match="field-type" use="#identifier" />
<xsl:key name="kEnumID" match="enum" use="#identifier" />
<xsl:key name="k1" match="entry" use="#key"/>
<xsl:variable name="map-doc" select="document('../transform/Properties.properties')"/>
<xsl:template match="/">
<xsl:apply-templates select="repository/custom/entity/field"/>
</xsl:template>
<xsl:template match="field">
<xsl:copy>
<field>
<xsl:value-of select="#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="name"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="editable"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="visible"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="visible-from-top"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="max-length"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="exportPurpose"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="importPurpose"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="upper-bound"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="average-length"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="active"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="multiline"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="display-by-default"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="richtext"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="value"/>
<xsl:text>|</xsl:text>
</field>
<category-ref-name>
<xsl:value-of select="key('kCategory', #category-ref)/#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kCategory', #category-ref)/name"/>
<xsl:text>|</xsl:text>
</category-ref-name>
<field-type-ref>
<xsl:value-of select="substring-after(key('kFieldID', #field-type-ref)/persistence-class-name, 'java.lang.')"/>
<xsl:text>|</xsl:text>
</field-type-ref>
<proxy-entity-ref>
<xsl:value-of select="key('kFieldID', #field-type-ref)/#proxy-ref"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kFieldID', #field-type-ref)/lower-bound"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kFieldID', #field-type-ref)/range-min"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kFieldID', #field-type-ref)/range-max"/>
<xsl:text>|</xsl:text>
</proxy-entity-ref>
<enum-ref-name>
<xsl:value-of select="key('kEnumID', #enum-ref)/#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kEnumID', #enum-ref)/name"/>
<xsl:text>|</xsl:text>
<xsl:for-each select="key('kEnumID', #enum-ref)/entry">
<xsl:value-of select="#label"/>
<xsl:text>;</xsl:text>
</xsl:for-each>
<xsl:text>|</xsl:text>
<xsl:for-each select="key('kEnumID', #enum-ref)/entry">
<xsl:value-of select="#external-code"/>
<xsl:text>;</xsl:text>
</xsl:for-each>
<xsl:text>|</xsl:text>
<xsl:for-each select="key('kEnumID', #enum-ref)/entry">
<xsl:value-of select="#key"/>
<xsl:text>;</xsl:text>
</xsl:for-each>
<xsl:text>|</xsl:text>
</enum-ref-name>
<entity>
<xsl:value-of select="../#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="../name"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="../#parentEntities-ref"/>
</entity>
</xsl:copy>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Properties
field.Article.AclFlag.name= Object right type
category.MasterData= Header data
enum.Acls.name=Object right types
The csv file is also not formatted well.
Sample XML
<?xml version="1.0" encoding="UTF-8"?>
<repository>
<types>
<entity-type identifier="ArticleType" identifying-field-type-ref="ArticleType.Id">
<object-name>article</object-name>
<class-name>ArticleType</class-name>
<persistence-xpath>/Article</persistence-xpath>
<persistence-class-name>db.model.Article</persistence-class-name>
<lower-bound>1</lower-bound>
<upper-bound>1</upper-bound>
<field-type identifier="ArticleType.AclFlag">
<object-name>aclFlag</object-name>
<class-name>commons.AclFlags</class-name>
<persistence-xpath>/aclFlag</persistence-xpath>
<persistence-class-name>java.lang.Integer</persistence-class-name>
<fragment-column-access>Article.AclFlag</fragment-column-access>
<internal>true</internal>
<lower-bound>0</lower-bound>
<range-min></range-min>
<range-max></range-max>
<min-length>0</min-length>
</field-type>
</entity-type>
</types>
<custom>
<category identifier="master-data" order="1">
<name>%category.MasterData</name>
</category>
<enum identifier="Enum.Acls">
<name>%enum.Acls.name</name>
<description>%enum.Acls.description</description>
<class-name>com.heiler.ppm.repository.enumerations.StdEnumProvider</class-name>
<key-class-name>commons.AclFlags</key-class-name>
<entry label="%enum.Acls.entry.0" external-code="0" key="0"/>
<entry label="%enum.Acls.entry.1" external-code="1" key="1"/>
<entry label="%enum.Acls.entry.2" external-code="2" key="2"/>
<entry label="%enum.Acls.entry.3" external-code="3" key="3"/>
<entry label="%enum.Acls.entry.4" external-code="4" key="4"/>
<entry label="%enum.Acls.entry.5" external-code="5" key="5"/>
<entry label="%enum.Acls.entry.6" external-code="6" key="6"/>
</enum>
<entity entity-type-ref="ArticleType" identifier="Article" parentEntities-ref="Product2G Variant">
<name>%entity.Article.name</name>
<description>%entity.Article.description</description>
<label-pattern-short>{Article.SupplierAID}</label-pattern-short>
<label-pattern-long>{Article.SupplierAID} - {ArticleLang.DescriptionShort}</label-pattern-long>
<label-pattern-description>{ArticleLang.DescriptionLong}</label-pattern-description>
<field identifier="Article.AclFlag" category-ref="master-data" enum-ref="Enum.Acls" field-type-ref="ArticleType.AclFlag">
<name>%field.Article.AclFlag.name</name>
<description>%field.Article.AclFlag.description</description>
<editable>false</editable>
<visible>true</visible>
<visible-from-top>true</visible-from-top>
<help-context></help-context>
<mergeable>false</mergeable>
</field>
</custom>
</repository>
Sample Properties in XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Default</comment>
<entry key="field.Article.AclFlag.name">Object right type</entry>
<entry key="category.MasterData">Header data</entry>
<entry key="enum.Acls.name">Object right types</entry>
</properties>
Any help is much appreciated.

Given that your properties are of this form
<entry key="field.Article.AclFlag.name">Object right type</entry>
You can define a key to look up entry elements by their key attribute, and I see such a key already exists in your XSLT
<xsl:key name="k1" match="entry" use="#key"/>
Next, ensure you have a variable reference to your external XML document
<xsl:variable name="map-doc" select="document('../transform/Properties.properties.xml')"/>
Then, to look up a value from the properties you can do this (obviously replacing the second argument with the actual value you want to look up):
<xsl:variable name="test" select="'enum.Acls.name'" />
<xsl:value-of select="key('k1', $test, $map-doc)" />
Note this form of the key function, with a third parameter, is only valid in XSLT 2.0. If you were to do <xsl:value-of select="key('k1', $test) />, it would look for the value in the input XML, not your properties XML.
In XSLT 1.0 you could do this to change the document context for the key
<xsl:variable name="test" select="'enum.Acls.name'" />
<xsl:for-each select="$map-doc">
<xsl:value-of select="key('k1', $test)" />
</xsl:for-each>
Alternatively, do it without a key
<xsl:variable name="test" select="'enum.Acls.name'" />
<xsl:value-of select="$map-doc//entry[#key=$test]" />

Related

How to concatenate child elements under one parent node by | separator in XSLT?

I have an issue while concatenating all the the child elements under parent element.
Here is the source data
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="abc">
<attr attr-name="A">
<new-value>123</new-value>
</attr>
<attr attr-name="B">
<new-value>99</new-value>
</attr>
<attr attr-name="B">
<new-value>77</new-value>
</attr>
<attr attr-name="C">
<new-value>1</new-value>
<new-value>2</new-value>
<new-value>3</new-value>
<new-value>4</new-value>
<new-value>5</new-value>
<new-value>6</new-value>
<new-value>7</new-value>
<new-value>8</new-value>
</attr>
<attr attr-name="D">
<new-value>
<child1>567</child1>
<child2>2</child2>
</new-value>
</attr>
<attr attr-name="E">
<new-value>
<child3>890</child3>
<child4>3</child4>
</new-value>
</attr>
</PurchaseOrder>
XSLT Transformation used
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns0:PurchaseOrderMSG>>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/materials/material">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="attr" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:if test="position()!=1">
<ns0:text>|</ns0:text>
</xsl:if>
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
The expected Result should be
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>123</A>
<B>99</B>
<B>77</B>
<C>1|2|3|4|5|6|7|8</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
The output that is coming with the XSLT I have used is as below. Separator logic is not working.
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>123</A>
<B>99</B>
<B>77</B>
<C>12345678</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
I have tried logic mentioned in some blogs but no luck
XSLT merging/concatenating values of siblings nodes of same name into single node
Concatenate several child items into one child item using XSLT
You need to modify the <xsl:otherwise> condition to handle the values in <new-value> node as below. If the count of <new-value> child nodes is > 1, then perform the concatenation using the separator else just output the value as is.
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:choose>
<xsl:when test="count(new-value) > 1">
<xsl:for-each select="new-value">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
The complete XSLT and the output is as below.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns0:PurchaseOrderMSG>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/materials/material">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="attr" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:choose>
<xsl:when test="count(new-value) > 1">
<xsl:for-each select="new-value">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
Output
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order id="abc">
<A>123</A>
<B>99</B>
<B>77</B>
<C>1|2|3|4|5|6|7|8</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>

XSLT -One output in SSIS

I have a XML file when I use SSIS XML as Source it is generating multiple outputs. I am using XML task to design XSLT for getting single output.
Following is the XML. I need only single output in SSIS. How to write XSLT for the same?
<?xml version="1.0" encoding="utf-8"?>
<Test>
<iden type="shiftid">100</iden >
<iden type="trailid">25</iden >
<TestHeader>
<SequenceNum>111</SequenceNum>
<TestType>CONTAINER</TestType>
<hub role="origin">
<name />
<hubId>030</hubId>
<hubType>New</hubType>
</hub>
<hub role="Dest">
<name />
<hubId>0757</hubId>
<hubType>hublet</hubType>
</hub>
<TestItem>
<lineNumber>1</lineNumber>
<container>1005</container>
<conditionCode>OK</conditionCode>
<hub role="originator">
<name />
<hubId>TRANS-SHIPMENT</hubId>
<hubType>depot</hubType>
</hub>
</TestItem>
<TestItem>
<lineNumber>2</lineNumber>
<container>1005</container>
<conditionCode>OK</conditionCode>
<hub role="originator">
<name />
<hubId>SHIPMENT</hubId>
<hubType>depot</hubType>
</hub>
</TestItem>
<TestItem>
<lineNumber>1</lineNumber>
<container>1006</container>
<conditionCode>OK</conditionCode>
<hub role="originator">
<name />
<hubId>SHIPMENT</hubId>
<hubType>depot</hubType>
</hub>
</TestItem>
</TestHeader>
<TestContainer type="RAPP" id="1005">
<uom hb="m3" type="board">10.0729</uom>
</TestContainer>
<TestContainer type="RAMP" id="1006">
<uom hb="m3" type="board">10.0729</uom>
</TestContainer>
</Test>
I have struggled and comeup with XSLT as follows
<?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:output method="xml" indent="yes"/>
<xsl:template name="Iden" match="Test">
<xsl:for-each select="../iden">
<xsl:choose>
<xsl:when test="#type='shiftid'">
<ShipmentID>
<xsl:value-of select="."/>
</ShipmentID>
</xsl:when>
<xsl:when test="#type='trailid'">
<TrailerID>
<xsl:value-of select="."/>
</TrailerID>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template name="Item" match="Test">
<xsl:param name="MatchContainer"/>
<xsl:for-each select="//TestItem">
<xsl:if test="$MatchContainer=container">
<Testitem>
<lineNumber>
<xsl:value-of select="lineNumber"/>
</lineNumber>
<containerTestitemID>
<xsl:value-of select="container"/>
</containerTestitemID>
<ConditionCode>
<xsl:value-of select="conditionCode"/>
</ConditionCode>
<Originator>
<xsl:value-of select="hub[#role='originator']/hubId"/>
</Originator>
<OriginatorType>
<xsl:value-of select="hub[#role='originator']/hubType"/>
</OriginatorType>
</Testitem>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="container" match="Test">
<xsl:for-each select="../TestContainer">
<Testcontainerlist>
<ContainerType>
<xsl:value-of select="#type"/>
</ContainerType>
<ContainerID>
<xsl:value-of select="#id"/>
</ContainerID>
<Volume>
<xsl:value-of select="uom"/>
</Volume>
<VolumeUOM>
<xsl:value-of select="volume/#hb"/>
</VolumeUOM>
<VolumeType>
<xsl:value-of select="volume/#type"/>
</VolumeType>
<xsl:call-template name="Item">
<xsl:with-param name="MatchContainer" select="#id"/>
</xsl:call-template>
</Testcontainerlist>
</xsl:for-each>
</xsl:template>
<xsl:template name="Header" match="Test">
<xsl:for-each select="TestHeader">
<Test>
<xsl:call-template name="Iden">
</xsl:call-template>
<dropSequenceNumber>
<xsl:value-of select="SequenceNum"/>
</dropSequenceNumber>
<TestType>
<xsl:value-of select="TestType"/>
</TestType>
<OriginID>
<xsl:value-of select="hub[#role='origin']/hubId"/>
</OriginID>
<DestinationID>
<xsl:value-of select="hub[#role='Dest']/hubId"/>
</DestinationID>
<OriginType>
<xsl:value-of select="hub[#role='origin']/hubType"/>
</OriginType>
<DestinationType>
<xsl:value-of select="hub[#role='Dest']/hubType"/>
</DestinationType>
<xsl:call-template name="container">
</xsl:call-template>
</Test>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
My desired output should be as follows
<?xml version="1.0" encoding="utf-8"?>
<Test>
<List>
<ShipmentID>100</ShipmentID>
<TrailerID>25</TrailerID>
<dropSequenceNumber>111</dropSequenceNumber>
<TestType>CONTAINER</TestType>
<OriginID>030</OriginID>
<DestinationID>0757</DestinationID>
<OriginType>New</OriginType>
<DestinationType>hublet</DestinationType>
<ContainerType>RAPP</ContainerType>
<ContainerID>1005</ContainerID>
<Volume>10.0729</Volume>
<VolumeUOM>m3</VolumeUOM>
<VolumeType>board</VolumeType>
<lineNumber>1</lineNumber>
<containerTestitemID>1005</containerTestitemID>
<ConditionCode>OK</ConditionCode>
<Originator>TRANS-SHIPMENT</Originator>
<OriginatorType>depot</OriginatorType>
</List>
<List>
<ShipmentID>100</ShipmentID>
<TrailerID>25</TrailerID>
<dropSequenceNumber>111</dropSequenceNumber>
<TestType>CONTAINER</TestType>
<OriginID>030</OriginID>
<DestinationID>0757</DestinationID>
<OriginType>New</OriginType>
<DestinationType>hublet</DestinationType>
<ContainerType>RAPP</ContainerType>
<ContainerID>1005</ContainerID>
<Volume>10.0729</Volume>
<VolumeUOM>m3</VolumeUOM>
<VolumeType>board</VolumeType>
<lineNumber>2</lineNumber>
<containerTestitemID>1005</containerTestitemID>
<ConditionCode>OK</ConditionCode>
<Originator>SHIPMENT</Originator>
<OriginatorType>depot</OriginatorType>
</List>
<List>
<ShipmentID>100</ShipmentID>
<TrailerID>25</TrailerID>
<dropSequenceNumber>111</dropSequenceNumber>
<TestType>CONTAINER</TestType>
<OriginID>030</OriginID>
<DestinationID>0757</DestinationID>
<OriginType>New</OriginType>
<DestinationType>hublet</DestinationType>
<ContainerType>RAMP</ContainerType>
<ContainerID>1006</ContainerID>
<Volume>10.0729</Volume>
<VolumeUOM>m3</VolumeUOM>
<VolumeType>board</VolumeType>
<lineNumber>1</lineNumber>
<containerTestitemID>1006</containerTestitemID>
<ConditionCode>OK</ConditionCode>
<Originator>SHIPMENT</Originator>
<OriginatorType>depot</OriginatorType>
</List>
</Test>

XSLT 1.0 Split comma seperated string into named nodes

A customer supplied XML contains the delivery address as a comma separated string, I would like to split this string into named nodes using XSLT 1.0.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
Company, Streetaddress 20, 1234 AA, City
</text>
</root>
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress 20</ADDRESS>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</text>
</root>
I tried several recursive templates for XSLT 1.0 which do a fine job splitting but the resulting nodes are identically named.
If possible, how can this be achieved using XSLT 1.0?
Does it have to be a recursive template? How about a straight-forward chain of substring-before and substring-after like this:
<xsl:template match="text">
<xsl:copy>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($s1, ','))"/>
</ADDRESS>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</xsl:copy>
</xsl:template>
For the fun of it, here is a generic version using a recursive template.
<xsl:template match="text">
<xsl:copy>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="'COMPANY,ADDRESS,ZIPCODE,CITY'"/>
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="parse-comma-separated">
<xsl:param name="elements"/>
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($elements, ',')">
<xsl:element name="{normalize-space(substring-before($elements, ','))}">
<xsl:value-of select="normalize-space(substring-before($text, ','))"/>
</xsl:element>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="substring-after($elements, ',')"/>
<xsl:with-param name="text" select="substring-after($text, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{normalize-space($elements)}">
<xsl:value-of select="normalize-space($text)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
As the supplied XML only had the address as single node (Streettaddress 20) I added a second variable to split the address string into streetaddress and housenumber.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="text">
<root>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<xsl:variable name="address_temp" select="normalize-space(substring-before($s1, ','))"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($address_temp, ' '))"/>
</ADDRESS>
<HOUSENUMBER>
<xsl:value-of select="normalize-space(substring-after($address_temp, ' '))"/>
</HOUSENUMBER>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</root>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress</ADDRESS>
<HOUSENUMBER>20</HOUSENUMBER>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</root>

XSLT - convert XML file

I would like to convert xml file format to another format;
using XSL version 1.0 or 2.0.
Input XML file:
<ROOT_XML>
<Struct id="_6" name="Result" context="_1" members="_9 _10 _11 _13 _14 "/>
<FundamentalType id="_7" name="int" size="32" align="32"/>
<FundamentalType id="_8" name="float" size="32" align="32"/>
<Field id="_9" name="angle" type="_8" offset="0" context="_6"/>
<Field id="_10" name="row" type="_7" offset="32" context="_6"/>
<Field id="_11" name="cloth" type="_18" offset="96" context="_6"/>
<Destructor id="_13" name="EmptyClass" artificial="1" throw="" context="_6">
</Destructor>
<Constructor id="_14" name="Result" context="_6">
<Argument type="_20" location="f0:2" file="f0" line="2"/>
</Constructor>
<Constructor id="_15" name="Result" context="_6"/>
<FundamentalType id="_17" name="unsigned int" size="32" align="32"/>
<ArrayType id="_18" min="0" max="29u" type="_21" size="240" align="8"/>
<ReferenceType id="_19" type="_6" size="32" align="32"/>
<ReferenceType id="_20" type="_6c" size="32" align="32"/>
<FundamentalType id="_21" name="char" size="8" align="8"/>
</ROOT_XML>
Output XML file:
<Struct Result>
<Fields>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Fields>
</Struct>
The is an example on how to parse the 'members' attribute list
http://www.w3.org/1999/XSL/Transform" version = "1.0">
<xsl:template match="/ROOT_XML/Struct">
<Struct><xsl:value-of select="name"/>
<xsl:choose>
<xsl:when test="boolean(./#members)">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="./#members"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</Struct>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<member>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
</member>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<member>
<xsl:value-of select="$list"/>
</member>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This code is a starting point to extract the relevant id's from the 'members' attribute of the 'Struct' node, and later be used to emit only the 'Field' nodes.
In addition, the output XML file might contains more than one 'Struct' node,
For Instance:
<Struct Result>
<Fields>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Fields>
</Struct>
<Struct Answer>
<Fields>
<Field name="direction" type="float" size="32"/>
<Field name="col" type="int" size="32"/>
<Field name="paper" type="char" size="232"/>
</Fields>
</Struct>
Thanks for the reply. still, I would like to emphasize the logic on how to get the xml output.
The xslt processor needs to parse the 'members' attribute of the Struct node.
The 'members' attribute is a list of Field's ids.
In the above example:
Only "members=_9 _10 _11" are Field nodes! and therefore they should be output as done by Michael previously.
The rest of the items in the list are omitted (i.e. members="_13 _14")
The combined code: (I need assistance to continue...)
http://www.w3.org/1999/XSL/Transform" version = "1.0">
<Struct><xsl:value-of select="name"/>
<xsl:choose>
<xsl:when test="boolean(./#members)">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="./#members"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</Struct>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<member>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
<!-- TODO: select holds the member's id...
Q: how should we continue from here?? -->
</member>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<member>
<xsl:value-of select="$list"/>
<!-- TODO: select holds the member's id...
Q: how should we continue from here?? -->
</member>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:template match="ROOT_XML">
<Struct>
<xsl:apply-templates select="Field"/>
</Struct>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example input, will return:
<?xml version="1.0" encoding="UTF-8"?>
<Struct>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Struct>
How this works:
If a Field's type matches an id of a FundamentalType, then
the type and the size values are looked up from the matching
FundamentalType;
If a Field's type matches an id of an ArrayType, then the size value is looked up from the matching ArrayType, while the type value is looked up from the FundamentalType whose id matches the type attribute of the ArrayType.
Edit:
If you want each Struct to include only Fields whose id is listed in its members attribute, you can do it this way:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="field" match="Field" use="#id" />
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:variable name="xml" select="/" />
<xsl:template match="/ROOT_XML">
<root>
<xsl:for-each select="Struct">
<Struct name="{#name}">
<xsl:apply-templates select="key('field', tokenize(#members, ' '))"/>
</Struct>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
</xsl:stylesheet>
Edit 2
The same thing in XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="field" match="Field" use="#id" />
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:variable name="xml" select="/" />
<xsl:template match="/ROOT_XML">
<root>
<xsl:for-each select="Struct">
<xsl:variable name="members">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="#members"/>
</xsl:call-template>
</xsl:variable>
<Struct name="{#name}">
<xsl:apply-templates select="key('field', exsl:node-set($members)/token)"/>
</Struct>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<token>
<xsl:value-of select="substring-before($text, $delimiter)"/>
</token>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$text"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

XML to CSV conversion

I have a scenario where I need to convert the input XML to a CSV file. The output should have values for every attribute with their respective XPATH.
For example: If my input is
<School>
<Class>
<Student name="" class="" rollno="" />
<Teacher name="" qualification="" Employeeno="" />
</Class>
</School>
The expected output would be:
School/Class/Student/name, School/Class/Student/class, School/Class/Student/rollno,
School/Class/Teacher/name, School/Class/Teacher/qualification, School/Class/Teacher/Employeeno
An example does not always embody a rule. Assuming you want a row for each element that has any attributes, no matter where in the document it is, and a column for each attribute of an element, try:
Edit:
This is an improved version, corrected to work properly with nested elements.
<?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" encoding="UTF-8"/>
<xsl:template match="*">
<xsl:param name="path" />
<xsl:variable name="newpath" select="concat($path, '/', name())" />
<xsl:apply-templates select="#*">
<xsl:with-param name="path" select="$newpath"/>
</xsl:apply-templates>
<xsl:if test="#*">
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:apply-templates select="*">
<xsl:with-param name="path" select="$newpath"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#*">
<xsl:param name="path" />
<xsl:value-of select="substring(concat($path, '/', name()), 2)"/>
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to the following test input:
<Root>
<Parent parent="1" parent2="1b">
<Son son="11" son2="11b"/>
<Daughter daughter="12" daughter2="12b">
<Grandson grandson="121" grandson2="121b"/>
<Granddaughter granddaughter="122" granddaughter2="122b"/>
</Daughter>
<Sibling/>
</Parent>
</Root>
the result is:
Root/Parent/parent, Root/Parent/parent2
Root/Parent/Son/son, Root/Parent/Son/son2
Root/Parent/Daughter/daughter, Root/Parent/Daughter/daughter2
Root/Parent/Daughter/Grandson/grandson, Root/Parent/Daughter/Grandson/grandson2
Root/Parent/Daughter/Granddaughter/granddaughter, Root/Parent/Daughter/Granddaughter/granddaughter2
Note that the number of columns in each row can vary - this is often unacceptable in a CSV document.