Reputation: 427
I'm having a xml document which I would like to transform from:
<?xml version="1.0" encoding="UTF-8"?>
<import:configuration xmlns:import="http://schemas.company.com/wsdl/domain/v2/import">
<import:input>
<import:file headers="1" group="MAPPING">
<import:name>file001.txt</import:name>
<import:separator><![CDATA[;]]></import:separator>
<import:table>TAB00008_TECSPEC</import:table>
<import:field primary="true">
<import:name>VEMAR</import:name>
<import:target>VEMAR</import:target>
<import:type dbs="oracle" type="text">VARCHAR(3)</import:type>
<import:reference>
<import:table>TAB00006_TECSPEC</import:table>
<import:field>VEMAR</import:field>
</import:reference>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field primary="true">
<import:name>VENR</import:name>
<import:target>VENR</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(7)</import:type>
<import:reference>
<import:table>TAB00006_TECSPEC</import:table>
<import:field>VENR</import:field>
</import:reference>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field primary="true">
<import:name>KNR</import:name>
<import:target>KNR</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(9)</import:type>
<import:reference>
<import:table>TAB00003_TECSPEC</import:table>
<import:field>KNR</import:field>
</import:reference>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field>
<import:name>DATNEU</import:name>
<import:target>DATNEU</import:target>
<import:type dbs="oracle" type="date" format="YYYY.MM.DD">DATE</import:type>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field>
<import:name>VTYPE</import:name>
<import:target>VTYPE</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(1)</import:type>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:description><![CDATA[]]></import:description>
</import:file>
</import:input>
</import:configuration>
To:
<?xml version="1.0" encoding="UTF-8"?>
<import:configuration xmlns:import="http://schemas.company.com/wsdl/domain/v2/import">
<import:input>
<import:file headers="1" group="MAPPING">
<import:name>file001.txt</import:name>
<import:separator><![CDATA[;]]></import:separator>
<import:table>TAB00008_TECSPEC</import:table>
<import:field primary="true">
<import:name>VEMAR</import:name>
<import:target>VEMAR</import:target>
<import:type dbs="oracle" type="text">VARCHAR(3)</import:type>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field primary="true">
<import:name>VENR</import:name>
<import:target>VENR</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(7)</import:type>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field primary="true">
<import:name>KNR</import:name>
<import:target>KNR</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(9)</import:type>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field>
<import:name>DATNEU</import:name>
<import:target>DATNEU</import:target>
<import:type dbs="oracle" type="date" format="YYYY.MM.DD">DATE</import:type>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:field>
<import:name>VTYPE</import:name>
<import:target>VTYPE</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(1)</import:type>
<import:description><![CDATA[]]></import:description>
</import:field>
<import:reference>
<import:table>TAB00006_TECSPEC</table>
<import:link>
<source>VEMAR</source>
<target>VEMAR</target>
</import:link>
<import:link>
<source>VENR</source>
<target>VENR</target>
</import:link>
</import:reference>
<import:reference>
<import:table>TAB00003_TECSPEC</table>
<import:link>
<source>KNR</source>
<target>KNR</target>
</import:link>
</import:reference>
<import:description><![CDATA[]]></import:description>
</import:file>
</import:input>
</import:configuration>
I want to group all references (</import:reference>
under <import:field>
) and grouped by table transform them to one element as described above.
I was reading this posting/question: XML to CSV with XSLT - Grouping nodes but I can't get this working to get the desired output.
My knowledge in xslt is not so deep. Can anybody give a hint how I could do this?
Upvotes: 0
Views: 612
Reputation: 338128
Your attempt is too complicated. Things I've noted:
<xsl:copy>
and <xsl:copy-of>
to make copies of input nodes. You don't need to re-create them manually.Your task is of the "I need a copy of the input file, but with a small modification" variety.
The basis for these task always is the identity transform.
<xsl:transform
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
This makes a verbatim copy of the input. Now to your small modifications. XSLT works through template matching, so we need an <xsl:template>
for everything we want to make a modification to.
You want the <import:reference>
removed from <import:field>
in the final output. That's easy, write a template for them that produces no output:
<xsl:template match="import:reference" />
You want a new <import:reference>
inside the <import:file>
, one per <import:table>
. That's not so difficult, either. Write a template that matches import:file
, copies most of it and appends something for each import:table
group.
<xsl:template match="import:file">
<xsl:copy>
<xsl:apply-templates select="@* | node()" />
<xsl:for-each-group select=".//import:reference" group-by="import:table">
<xsl:copy>
<xsl:copy-of select="import:table" />
<import:link>
<source><xsl:value-of select="../import:target" /></source>
<target><xsl:value-of select="import:field" /></target>
</import:link>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
Putting it together:
<xsl:transform
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:import="http://schemas.company.com/wsdl/domain/v2/import"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="import:file">
<xsl:copy>
<xsl:apply-templates select="@* | node()" />
<xsl:for-each-group select=".//import:reference" group-by="import:table">
<xsl:copy>
<xsl:copy-of select="import:table" />
<import:link>
<source><xsl:value-of select="../import:target" /></source>
<target><xsl:value-of select="import:field" /></target>
</import:link>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="import:reference" />
</xsl:transform>
Produces pretty much exactly your desired result:
<import:configuration xmlns:import="http://schemas.company.com/wsdl/domain/v2/import">
<import:input>
<import:file headers="1" group="MAPPING">
<import:name>file001.txt</import:name>
<import:separator>;</import:separator>
<import:table>TAB00008_TECSPEC</import:table>
<import:field primary="true">
<import:name>VEMAR</import:name>
<import:target>VEMAR</import:target>
<import:type dbs="oracle" type="text">VARCHAR(3)</import:type>
<import:description/>
</import:field>
<import:field primary="true">
<import:name>VENR</import:name>
<import:target>VENR</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(7)</import:type>
<import:description/>
</import:field>
<import:field primary="true">
<import:name>KNR</import:name>
<import:target>KNR</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(9)</import:type>
<import:description/>
</import:field>
<import:field>
<import:name>DATNEU</import:name>
<import:target>DATNEU</import:target>
<import:type dbs="oracle" type="date" format="YYYY.MM.DD">DATE</import:type>
<import:description/>
</import:field>
<import:field>
<import:name>VTYPE</import:name>
<import:target>VTYPE</import:target>
<import:type dbs="oracle" type="numeric">NUMBER(1)</import:type>
<import:description/>
</import:field>
<import:description/>
<import:reference>
<import:table>TAB00006_TECSPEC</import:table>
<import:link>
<source>VEMAR</source>
<target>VEMAR</target>
</import:link>
</import:reference>
<import:reference>
<import:table>TAB00003_TECSPEC</import:table>
<import:link>
<source>KNR</source>
<target>KNR</target>
</import:link>
</import:reference>
</import:file>
</import:input>
</import:configuration>
This does not include the xmlns:xsi
attribute in the output (wasn't part of the original question). You can add that in the same fashion, by writing a template that modifies import:configuration
:
<xsl:template match="import:configuration">
<xsl:copy>
<xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance">http://schemas.company.com/wsdl/domain/v2/import C:/Users/Ruben/Downloads/tmp/new/xsd/import_config.xsd</xsl:attribute>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
You could move the xsi
namespace prefix declaration to the root level in the XSLT.
Upvotes: 1
Reputation: 427
This worked (it brings my near to my desired target):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="http://schemas.company.com/wsdl/domain/v2/import" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="ns0 xs fn">
<xsl:output method="xml" encoding="UTF-8" byte-order-mark="no" indent="yes"/>
<xsl:template match="/">
<configuration xmlns="http://schemas.company.com/wsdl/domain/v2/import">
<xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance" select="'http://schemas.company.com/wsdl/domain/v2/import C:/Users/Ruben/Downloads/tmp/new/xsd/import_config.xsd'"/>
<xsl:for-each select="ns0:configuration">
<input>
<xsl:for-each select="ns0:input/ns0:file">
<xsl:variable name="var1_group" as="node()?" select="@group"/>
<file>
<xsl:attribute name="headers" namespace="" select="xs:string(xs:integer(fn:string(@headers)))"/>
<xsl:if test="fn:exists($var1_group)">
<xsl:attribute name="group" namespace="" select="fn:string($var1_group)"/>
</xsl:if>
<name>
<xsl:sequence select="fn:string(ns0:name)"/>
</name>
<xsl:for-each select="ns0:directory">
<directory>
<xsl:sequence select="fn:string(.)"/>
</directory>
</xsl:for-each>
<separator>
<xsl:sequence select="fn:string(ns0:separator)"/>
</separator>
<table>
<xsl:sequence select="fn:string(ns0:table)"/>
</table>
<xsl:for-each select="ns0:field">
<xsl:variable name="var2_primary" as="node()?" select="@primary"/>
<field>
<xsl:if test="fn:exists($var2_primary)">
<xsl:attribute name="primary" namespace="" select="xs:string(xs:boolean(fn:string($var2_primary)))"/>
</xsl:if>
<name>
<xsl:sequence select="fn:string(ns0:name)"/>
</name>
<target>
<xsl:sequence select="fn:string(ns0:target)"/>
</target>
<xsl:for-each select="ns0:type">
<type>
<xsl:sequence select="(./@node(), ./node())"/>
</type>
</xsl:for-each>
<description>
<xsl:sequence select="fn:string(ns0:description)"/>
</description>
</field>
</xsl:for-each>
<xsl:for-each select="ns0:field">
<xsl:variable name="var3_current" as="node()" select="."/>
<xsl:for-each select="ns0:reference">
<reference>
<table>
<xsl:sequence select="fn:string(ns0:table)"/>
</table>
<link>
<source>
<xsl:sequence select="fn:string($var3_current/ns0:target)"/>
</source>
<target>
<xsl:sequence select="fn:string(ns0:field)"/>
</target>
</link>
</reference>
</xsl:for-each>
</xsl:for-each>
<description>
<xsl:sequence select="fn:string(ns0:description)"/>
</description>
</file>
</xsl:for-each>
</input>
<description>
<xsl:sequence select="fn:string(ns0:description)"/>
</description>
</xsl:for-each>
</configuration>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0