Search code examples
xmlxslt-1.0xpath-1.0

xsl 1.0 code to create an distinct elements not working


I have this xsl 1.0 call-template. It takes the two xml-elements, given as parameters and returns an element created from both parameters, but with distinct Elements. All doubles should be removed

<xsl:template name="distinctElements">
        <xsl:param name="xml1"/>
        <xsl:param name="xml2"/>
        
        <xsl:variable name="combined-nodes">
          <xsl:copy-of select="$xml1"/>
          <xsl:copy-of select="$xml2"/>
        </xsl:variable>
        
        <xsl:variable name="distinct-elements">
          <xsl:for-each select="$combined-nodes/*[not(. = preceding-sibling::*)]">
            <xsl:copy-of select="."/>
          </xsl:for-each>
        </xsl:variable>
        
        <xsl:for-each select="$distinct-elements/*">
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:template>

My problem is, it does not work. Specifically this code makes problems:

<xsl:variable name="distinct-elements">
 <xsl:for-each select="$combined-nodes/*[not(. = preceding-sibling::*)]">
    <xsl:copy-of select="."/>
 </xsl:for-each>
</xsl:variable>

It seems as it is not possible to iterate through "$combined-nodes" at all. This code-fragment works if I use $xml1 or $xml2 instead of $combined-nodes.

I don't get it. Does someone see what is my problem here?

Thank you for your help.

I swapped $combined-nodes with $xml1 and $xml2 and the code worked. Only when using the combined element, the code does not work.

EDIT: I loked at the muenchian method and will ivestigate that deeper. For the moment I do not really know how to adapt it into my special situation. Because the XML I have to handle is quite generic:

<someuserdata>
  <item>
    <name>foo</name>
    <value>bar</value>
  </item>
  <item>
    <name>foo</name>
    <value>bar</value>
  </item>
  ...
</someuserdata>
<someappointmentdata>
  <item>
    <name>foo</name>
    <value>bar</value>
  </item>
  <item>
    <name>foo</name>
    <value>bar</value>
  </item>
  ...
</someappointmentdata> 

It contains data from different databases with different information all combined in one xml-file, nested in different levels.

The xsl handles this xml in a generic way, just differed by checking for certain "Name"-Values.

i tried the code-example provided by John Ernst, but cannot get it to work. I decided to dismiss the usage of a call-template and tried to reduce the code to work just in the calling part of my code:

<xsl:template name="table_parent">  
        <xsl:param name="basis1" />
        <xsl:param name="basis2" />     
        <xsl:variable name="basis3">
            <xsl:copy-of select="xmlns:node-set($basis1 | $basis2)/*[not(. = preceding-sibling::*)]"/>
        </xsl:variable> 
        <xsl:for-each select="$basis3/*">

As you see, I just wanted to iterate through a distinct sum of my two xml-elements (basis1 and basis2).

but even this simple lines do not work. My prefix is xmlns, which I changed already in this example.

Shouldn't this work?

Thank you for your patience and your help

EDIT: I have some sample-code here: xml:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="TEST.xsl"?>
<Root>
    <source1>
        <Zusatzinformation>
            <item>
                <Name>Bemerkung</Name>
                <Wert>yyy</Wert>
                <Attribut>NEU</Attribut>
            </item>
            <item>
                <Name>Versicherungsart</Name>
                <Wert>-kein Eintrag-</Wert>
                <Attribut>Unverändert</Attribut>
            </item>
            <item>
                <Name>Zusatzversichert</Name>
                <Wert>nein</Wert>
                <Attribut>Unverändert</Attribut>
            </item>
            <Anordner>
                <CTSmartMitarbeiter>
                    <CTSmartId>
                        <item>
                            <Name>ID</Name>
                            <Wert>-1</Wert>
                            <Attribut>Unverändert</Attribut>
                        </item>
                    </CTSmartId>
                </CTSmartMitarbeiter>
            </Anordner>
        </Zusatzinformation>
    </source1>
    <source2>
        <Zusatzinformation>
            <item>
                <Name>Versicherungsart</Name>
                <Wert>-kein Eintrag-</Wert>
                <Attribut>Unverändert</Attribut>
            </item>
            <item>
                <Name>Zusatzversichert</Name>
                <Wert>nein</Wert>
                <Attribut>Unverändert</Attribut>
            </item>
            <Anordner>
                <CTSmartMitarbeiter>
                    <CTSmartId>
                        <item>
                            <Name>ID</Name>
                            <Wert>-1</Wert>
                            <Attribut>Unverändert</Attribut>
                        </item>
                    </CTSmartId>
                </CTSmartMitarbeiter>
            </Anordner>
        </Zusatzinformation>
    </source2>
</Root>

xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:exsl="http://exslt.org/common"                extension-element-prefixes="exsl">
    <xsl:template match="/">
        <xsl:variable name="source1" select="//source1"/>
        <xsl:variable name="source2" select="//source2"/>

        <xsl:variable name="combined-nodes" select="$source1 | $source2"/>
        
        <xsl:variable name="distinct-elements" select="exsl:node-set($combined-nodes)"/>

        <xsl:variable name="basis3">
            <xsl:copy-of select="exsl:node-set($distinct-elements)/*[not(. = preceding-sibling::*)]"/>
        </xsl:variable> 

        <xsl:for-each select="basis3/*">
            <xsl:value-of select="."/><xsl:text> - </xsl:text>
        </xsl:for-each>

    </xsl:template>
</xsl:stylesheet>

The xsl should output just a distinct list of:

Bemerkung Versicherungsart Zusatzversichert

Thank you all. With your help I came to this solution:

<xsl:template name="distinctElements">
  <xsl:param name="xml1"/>
  <xsl:param name="xml2"/>    
  
  <xsl:variable select="exsd:node-set($xml1|$xml2)" name="combined-nodes"/>   
  
  <xsl:variable name="result">
    <xsl:for-each select="$combined-nodes/item[count(. | key('item-by-name', Name)[1]) = 1]">
      <itemname><xsl:copy-of select="."/></itemname>          
    </xsl:for-each>
  </xsl:variable>         
 
  <xsl:copy-of select="$result"/>
</xsl:template>

Because I needed the output of the template as a nodeset, I converted it after the call:

<xsl:variable name="basis3Rtf">
    <xsl:call-template name="distinctElements">
        <xsl:with-param name="xml1" select="$basis1"/>
        <xsl:with-param name="xml2" select="$basis2"/>
    </xsl:call-template>
</xsl:variable>
<xsl:variable name="basis3" select="exsd:node-set($basis3Rtf)/itemname" />
<xsl:for-each select="$basis3/*">

Solution

  • Consider the following example:

    XML

    <Root>
        <source1>
            <Zusatzinformation>
                <item>
                    <Name>Bemerkung</Name>
                    <Wert>yyy</Wert>
                    <Attribut>NEU</Attribut>
                </item>
                <item>
                    <Name>Versicherungsart</Name>
                    <Wert>-kein Eintrag-</Wert>
                    <Attribut>Unverändert</Attribut>
                </item>
                <item>
                    <Name>Zusatzversichert</Name>
                    <Wert>nein</Wert>
                    <Attribut>Unverändert</Attribut>
                </item>
                <Anordner>
                    <CTSmartMitarbeiter>
                        <CTSmartId>
                            <item>
                                <Name>ID</Name>
                                <Wert>-1</Wert>
                                <Attribut>Unverändert</Attribut>
                            </item>
                        </CTSmartId>
                    </CTSmartMitarbeiter>
                </Anordner>
            </Zusatzinformation>
        </source1>
        <source2>
            <Zusatzinformation>
                <item>
                    <Name>Versicherungsart</Name>
                    <Wert>-kein Eintrag-</Wert>
                    <Attribut>Unverändert</Attribut>
                </item>
                <item>
                    <Name>Zusatzversichert</Name>
                    <Wert>nein</Wert>
                    <Attribut>Unverändert</Attribut>
                </item>
                <Anordner>
                    <CTSmartMitarbeiter>
                        <CTSmartId>
                            <item>
                                <Name>ID</Name>
                                <Wert>-1</Wert>
                                <Attribut>Unverändert</Attribut>
                            </item>
                        </CTSmartId>
                    </CTSmartMitarbeiter>
                </Anordner>
            </Zusatzinformation>
        </source2>
    </Root>
    

    Using (the first part of) Muenchian grouping:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8"/>
    
    <xsl:key name="item-by-name" match="item" use="Name" />
    
    <xsl:template match="/Root">
        <xsl:for-each select="*/Zusatzinformation/item[count(. | key('item-by-name', Name)[1]) = 1]">
            <xsl:value-of select="Name"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Result

    Bemerkung
    Versicherungsart
    Zusatzversichert
    

    Note that some XSLT 1.0 processors support a distinct() extension function which could make this task much easier.