3

I mean to add an attribute element to a node if and only if the source for that attribute's data exists.

In other words, I don't want to end up with empty attributes in case the source isn't matched by my rule.

<tok id="t1" fooID=""/> //not accepted
<tok id="t1" /> //instead of ^
<tok id="t1" fooID="bar"/> //accepted


Moreover, I would like to test if the source has more than 1 node corresponding to my current attribute, and if so, add another foo attribute with "source2". Here's what I'm currently using:

<xsl:template match="tokens/token">
<tok id="{@ID}" 
     ctag="{/root/myStuff/fooSources[1and2 how?]/fooSource[@fooID=current()/@ID]}" 
>
</tok>

The source XML is like this:

<root>
   <myStuff>
      <tokens>
         <token ID="bar"/>
         <token ID="anotherBar"/>
         <token ID="noFoo"/>
      </tokens>

      <fooSources>
         <fooSource fooID="bar"> kitten </fooSource>
         <fooSource fooID="anotherBar"> shovel </fooSource>
      </fooSources>

      <fooSources>
         <fooSource fooID="bar"> kitty </fooSource>
         <fooSource fooID="notAnotherBar"> fridge </fooSource>
      </fooSources>
   </myStuff>
</root>

The desired result would be this:

<tok id="bar" fooID="kitten" fooID_2="kitty"/>
<tok id="anotherBar" fooID="shovel"/> 
<tok id="noFoo" /> 

Thanks in advance for any help!

PS: I'd like to do this in xpath 1.0

1
  • Good question, +1. See my answer for a complete, short and easy XSLT 1.0 solution, :) Commented May 8, 2011 at 19:36

2 Answers 2

5
<foo>
   <xsl:if test="@bar">
      <xsl:attribute name="id">
         <xsl:value-of select="@bar"/>
      </xsl:attribute>
   </xsl:if>
</foo>
Sign up to request clarification or add additional context in comments.

Comments

3

PS: I'd like to do this in xpath 1.0

XPath is a query language for XML documents and as such it cannot modify any nodes of the document.

To produce the wanted result (changing element names and adding new attributes to elements) you have to use another language that is hosting XPath. The most appropriate such language, created especially with the goal to be used for XML transformations is XSLT.

This XSLT 1.0 transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kFooIdByVal" match="fooSource/@fooID"
  use="."/>

 <xsl:template match="token">
  <tok>
   <xsl:copy-of select="@*"/>
   <xsl:apply-templates
        select="key('kFooIdByVal',@ID)"/>
  </tok>
 </xsl:template>

 <xsl:template match="@fooID">
  <xsl:variable name="vattrName"
   select="concat('fooID',
                  substring(concat('_',position()),
                            1 div (position() >1)
                           )
                  )
   "/>

  <xsl:attribute name="{$vattrName}">
   <xsl:value-of select="normalize-space(..)"/>
  </xsl:attribute>
 </xsl:template>
 <xsl:template match="fooSources"/>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <myStuff>
        <tokens>
            <token ID="bar"/>
            <token ID="anotherBar"/>
            <token ID="noFoo"/>
        </tokens>
        <fooSources>
            <fooSource fooID="bar"> kitten </fooSource>
            <fooSource fooID="anotherBar"> shovel </fooSource>
        </fooSources>
        <fooSources>
            <fooSource fooID="bar"> kitty </fooSource>
            <fooSource fooID="notAnotherBar"> fridge </fooSource>
        </fooSources>
    </myStuff>
</root>

produces the wanted, correct result:

<tok ID="bar" fooID="kitten" fooID_2="kitty"/>
<tok ID="anotherBar" fooID="shovel"/>
<tok ID="noFoo"/>

Explanation:

  1. A template matching any token element creates a tok element that has all the existing attributes (if any) of the matchedtokenelement. It also applies templates on anyfooIDattribute, whose value is the same as the value of theIDattribute of the current (matched) node. If there isn't any suchfooID` attribute, no futher processing is done and no additional attributes are created.

  2. The template that matches a fooID attribute must generate a new attribute of the form "fooID_{N}" where N is the position of the current node (the matched fooID attribute) in the node-list (created by the <xsl:apply-templates> instruction that selected this current template for applying on this fooID attribute). If N is 1, we do not append to the start of the name ("fooID") the string "_"{N}.

  3. To avoid the side-effects of default XSLT processing we add a third template that matches any fooSources element and, (the template) has an empty body.

6 Comments

Beautiful. Thanks! I actually meant to say XSLT instead of xpath, got them mixed up...
Hmm, even though everything works fine when tested in firefox, it seems that the values returned by `<xsl:apply-templates select="key('kFooIdByVal',@ID)"/> don't show/work at all when called from a java program using Saxon 9.2... Any idea why that might happen? (there are no errors, it's just as if there were no matched values)
@Twodordan: Something in the Java program... This transformation was first run with Saxon -- then with other XSLT processors ... and all produce the same correct result.
@Dimitre You're right. The problem was (and still is) that I'm running my program as a web service. And the apache tomcat server (localhost) isn't using Saxon at all. Have to figure out how to get it to use saxon9.jar... All this time I thought I was using saxon but I wasn't ^_^
@Twodordan: The transformation is standard XSLT 1.0 and any compliant XSLT 1.0 processor (not only Saxon) executes it without problem.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.