Identity transformation problem... xml to xml transform

Here should go questions about transforming XML with XSLT and FOP.
bbaker
Posts: 6
Joined: Fri Jul 07, 2006 10:34 pm

Identity transformation problem... xml to xml transform

Post 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]
bbaker
Posts: 6
Joined: Fri Jul 07, 2006 10:34 pm

More clarification

Post 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.
sorin_ristache
Posts: 4141
Joined: Fri Mar 28, 2003 2:12 pm

Post 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
bbaker
Posts: 6
Joined: Fri Jul 07, 2006 10:34 pm

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

Post 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.
bbaker
Posts: 6
Joined: Fri Jul 07, 2006 10:34 pm

Got it working

Post 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.
george
Site Admin
Posts: 2095
Joined: Thu Jan 09, 2003 2:58 pm

Post 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
Post Reply