Page 1 of 1

Identity transformation problem... xml to xml transform

Posted: Fri Jul 07, 2006 11:26 pm
by bbaker
I already have a piece of code that will copy every attribute and node into a new xml document which is great! Here is the code:

<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>

In my xml file the <radiobutton> nodes are missing an attribute called 'text' that needs to be added during the transform. The 'text' attribute must be copied from the <dataitem> node higher up in the xml.

A really simple example xml could look like this:

BEFORE:

<datasource id="LikertScale">
<dataitem text="Unimportant" value="1"/>
</datasource>
<datasource id="RandomScale">
<dataitem text="High Importance" value="1"/>
</datasource>
<radiobutton groupname="1" datasource="LikertScale" dataitem="1" showlabel="false" />

AFTER the transform:

<datasource id="LikertScale">
<dataitem text="Unimportant" value="1"/>
</datasource>
<datasource id="RandomScale">
<dataitem text="High Importance" value="1"/>
</datasource>
<radiobutton groupname="1" datasource="LikertScale" dataitem="1" showlabel="false" text="Unimportant" />

**The only difference is the new attribute called "text" has been added to the <radiobutton>. If the "text" attribute was already ready there in the <radiobutton> then it shouldn't get added.

The "text" attribute was copied from the <dataitem> text attribute higher up in the xml. In order to find the right "text" value, xsl must compare dataitem attribute in <radiobutton> with the value attribute in <dataitem>. XSL should only look at <dataitem> nodes that are nested inside the appropriate <datasource>. In my example <datasource> id attribute matches <radiobutton> datasource attribute ("LikertScale"). If I were to change the <radiobutton> datasource attribute to "RandomScale", then the "text" attribute that should be copied is "High Importance".

If anyone has the slightest clue how to grapple this problem, I would greatly appreciate any help!

Thanks![/b]

More clarification

Posted: Fri Jul 07, 2006 11:42 pm
by bbaker
Realistically the xml/xml output could look more like this:

BEFORE:

<datasource id="LikertScale">
<dataitem text="Unimportant" value="1"/>
<dataitem text="Don't Care" value="2"/>
<dataitem text="Unsure" value="3"/>
<dataitem text="NA" value="4"/>
</datasource>
<datasource id="RandomScale">
<dataitem text="High Importance" value="1"/>
</datasource>
<radiobutton groupname="1" datasource="LikertScale" dataitem="1" showlabel="false" />
<radiobutton groupname="1" datasource="LikertScale" dataitem="2" showlabel="false" />


AFTER the transform:

<datasource id="LikertScale">
<dataitem text="Unimportant" value="1"/>
<dataitem text="Don't Care" value="2"/>
<dataitem text="Unsure" value="3"/>
<dataitem text="NA" value="4"/>
</datasource>
<datasource id="RandomScale">
<dataitem text="High Importance" value="1"/>
</datasource>
<radiobutton groupname="1" datasource="LikertScale" dataitem="1" showlabel="false" text="Unimportant" />
<radiobutton groupname="1" datasource="LikertScale" dataitem="2" showlabel="false" text="Don't Care" />

There could be N <radiobutton> nodes and N <datasource> nodes, and N <dataitem> nodes.

Posted: Mon Jul 10, 2006 4:44 pm
by sorin_ristache
Hello,

You can find below a stylesheet that adds the appropriate text attribute:

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>

<xsl:template match="radiobutton">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:if test="not(@text)">
<xsl:attribute name="text">
<xsl:value-of select="preceding-sibling::datasource[@id = current()/@datasource]/dataitem[@value = current()/@dataitem]/@text"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Regards,
Sorin

The text attribute is "", but it did get added.

Posted: Mon Jul 10, 2006 8:04 pm
by bbaker
I'm confused on what is happening inside the <xsl:value-of> statement. The XSL code appears to be correct, but I don't understand what is happening after the first current()/@datasource.

The text attribute is getting added, but its contents are empty.

Thanks for providing a full code example! That's always very helpful.

Got it working

Posted: Mon Jul 10, 2006 9:28 pm
by bbaker
I changed the value-of to read this instead:

<xsl:value-of select="//item/child::datasource[@id = current()/@datasource]/dataitem[@value = current()/@dataitem]/@text"/>

Now the text attribute contains the correct value!

I wonder if there is a cleaner way to do this (make it easier to read), like use a <xsl:apply-templates select="//item/child::datasource..."/> or similar to break up the XPath somehow.

Posted: Tue Jul 11, 2006 11:27 am
by george
An easier to read and better wrt performance solution is below. It uses xsl:key to define a map between dataitem#datasource and the corresponding dataitem. Then in the radiobutton just uses the key to get the dataitem referred by that radio button.

Add

Code: Select all


<xsl:key name="di" match="dataitem" use="concat(@value, '#', ../@id)"/>
and change the value-of in the text attribute as below:

Code: Select all


<xsl:attribute name="text">
<xsl:value-of select="key('di', concat(@dataitem, '#', @datasource))/@text"/>
</xsl:attribute>
Best Regards,
George