tags:

views:

1584

answers:

3

I've got some XML, for example purposes it looks like this:

<root>
    <field1>test</field1>
    <f2>t2</f2>
    <f2>t3</f2>
</root>

I want to transform it with XSLT, but I want to suppress the second f2 element in the output - how do I check inside my template to see if the f2 element already exists in the output when the second f2 element in the source is processed? My XSLT looks something like this at present:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:output method="xml" indent="no" omit-xml-declaration="yes" standalone="no" />
  <xsl:template match="/">
    <xsl:for-each select="./root">
      <output>
        <xsl:apply-templates />        
      </output>
    </xsl:for-each>
  </xsl:template>
  <xsl:template match="*" >
      <xsl:element name="{name(.)}">
        <xsl:value-of select="." />
      </xsl:element>
  </xsl:template>
</xsl:stylesheet>

I need to do some sort of check around the xsl:element in the template I think, but I'm not sure how to interrogate the output document to see if the element is already present.

Edit: Forgot the pre tags, code should be visible now!

+4  A: 

It depends how system wide you want to be.

i.e. Are you only concerned with elements that are children of the same parent, or all elements at the same level ('cousins' if you like) or elements anywhere in the document...

In the first situation you could check the preceding-sibling axis to see if any other elements exist with the same name.

<xsl:if test="count(preceding-sibling::node()[name()=name(current())])=0">
  ... do stuff in here.
</xsl:if>
samjudson
You could also simply make a template with that predicate, I would think.
James Sulak
This is perfect. Yes, I just need to suppress duplicates within the same parent, I want to explicitly allow the same elements within other parents - this works great and is a lot simpler than the route I was going down using keys and Muenchen method!
Code Trawler
+1  A: 

To only check (and warn you of a duplicate), you may find an example here

Something along the lines of:

<xsl:for-each-group select="collection(...)//@id" group-by=".">
  <xsl:if test="count(current-group()) ne 1">
    <xsl:message>Id value <xsl:value-of select="current-grouping-key()"/> is 
       duplicated in files
       <xsl:value-of select="current-group()/document-uri(/)" separator=" and
    "/></xsl:message>
  </xsl:if>
 </xsl:for-each-group>

To be modified to select all nodes within 'root' element.

As to remove the duplicate lines, you have another example here

That would look like:

<xsl:stylesheet>
  <xsl:key name="xyz" match="record[x/y/z]" use="x/y/z" />
  <xsl:variable name="noxyzdups" select="/path/to/record[generate-id(.) = generate-id(key('xyz', x/y/z))]" />
...
  <xsl:template ... >
    <xsl:copy-of "exslt:node-set($noxyzdups)" />
  </xsl:template>
</xsl:stylesheet>

x/y/z is the xpath expression that you want made unique. It can be concat(x,'-',@y,'-',z) or whatever you want.

Now I am not sure those two examples can easily be adapted to your case, but I just wanted to point out those two sources, in case it helps.

VonC
Yes, this was the road I was going down, and I think I could have got it to work, but the simplier check above is better suited for my needs. Thank you though.
Code Trawler
A: 

It's not possible to interrogate the output of your transform. It's also not possible to track the current state of your transform (i.e. keep track of what nodes you've emitted in a variable). Fundamentally, that's not how XSLT works. One of the costs of a side-effect-free programming environment is that you can't do things that have side effects. Oh well.

In your case, one way of accomplishing this would be to build a variable that contained a list of all of the source elements that could be transformed into the output element that you want to emit only once. Then check every node you're transforming against this list. If it's not in the list, emit it. If it's the first item in the list, emit it. Otherwise, don't.

Robert Rossney