You are not logged in.

  • Login

1

Tuesday, October 31st 2006, 12:50pm

XSLT: Sort and create list with unique entries

Hi all,

I have got the following issue, which I do not get resolved:
I have a source XML document containing the following structure (just an extract):

XML Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<root>
  <records>
    <record>
      <attribA>A</attribA>
      <attribB>A</attribB>
      <attribC>A</attribC>
      <attribD>A</attribD>
      <attribE>A</attribE>
      <user>
        <id>UserA</id>
        <roles>
           <role>
              <system>
                 <sysname>ABC</sysname>
                 <sysattr1>A</sysattr1>
                 <sysattr2>B</sysattr2>
                 <sysattr3>C</sysattr3>
              </system>
           </role>
           <role>
              <system>
                 <sysname>ABC</sysname>
                 <sysattr1>A</sysattr1>
                 <sysattr2>B</sysattr2>
                <sysattr3>C</sysattr3>
              </system>
           </role>
           <role>
              <system>
                 <sysname>XYZ</sysname>
                 <sysattr1>X</sysattr1>
                 <sysattr2>Y</sysattr2>
                <sysattr3>Z</sysattr3>
              </system>
           </role>
           <role>
              <system>
                 <sysname>DEF</sysname>
                 <sysattr1>D</sysattr1>
                 <sysattr2>E</sysattr2>
                <sysattr3>F</sysattr3>
              </system>
           </role>
        </roles>
      </user>
      <user>
        <id>UserB</id>
        <roles>
           <role>
              <system>
                 <sysname>ABC</sysname>
                 <sysattr1>A</sysattr1>
                 <sysattr2>B</sysattr2>
                 <sysattr3>C</sysattr3>
              </system>
              <system>
                 <sysname>XYZ</sysname>
                 <sysattr1>X</sysattr1>
                 <sysattr2>Y</sysattr2>
                <sysattr3>Z</sysattr3>
              </system>
           </role>
           <role>
              <system>
                 <sysname>DEF</sysname>
                 <sysattr1>D</sysattr1>
                 <sysattr2>E</sysattr2>
                <sysattr3>F</sysattr3>
              </system>
           </role>
        </roles>
      </user>
    </record>
  </records>
</root>



What I need to have is a unique node list, containing all systems with the same name exactly once. Therefore I created a for-each loop as follows:

XML Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<xsl:template match="/root/records/record">
   <xsl:for-each select="user/roles/role/system">
      <xsl:sort select="sysname" />
 
      <!-- Problem is, that I now get the following node list concerning "sysname": 
          ABC
          ABC
          ABC
          XYZ
          XYZ
          DEF
          DEF
 
          But to continue with this information in another section, I want to skip 
          the duplicates. Therefore I wanted to compare the current element with the 
          previous one. Using preceding-sibling, following-sibling, etc. does not work. 
      -->
 
   </xsl:for-each>
</xsl:template>


So my question is: How can I reduce the list to only have each entry seperatly and
still access to all tag values below <system>

I tried several things using preceding-sibling, etc. but it does not work. Also after sorting I could remeber the last one, but since I cannot change the value of a defined valriable, this does not work. So basically the question is when I loop through the structure ... how can I skip a a "node" which was already used in the previous loop step.

Your help is highly appreciated !!!

Thanks in advance,
Steffen

2

Tuesday, October 31st 2006, 1:09pm

Am einfachsten geht das mit XPath.
Der folgende Eintrag sollte dir eine Liste aller sysnames geben:

XML Code

1
//sysname[not (. = preceding::sysname)]


Dabei holt er sich alle sysnames die in keinem vorherigen Knoten in Dokument-Order vorkommen.

-----

The easiest thing is to use XPath. The code mentioned above returns all sysname elements that are unique.
The expression queries all sysnames except the ones that occured in any preceding node (in document order).

regards

3

Tuesday, October 31st 2006, 2:41pm

Hi,

basically this seems to work fine. I will test it within the complete structure in my real scenario. One more question: Since I am in a template matching all /root/records/record ... elements do I only get the list of sysnames ´belonging to the current context? Because this would be a requirement here.

Thank you,
Best Regards

4

Tuesday, October 31st 2006, 2:59pm

In the code pasted above you will get all sysnames.
But if you use

XML Code

1
.//sysname[not (. = preceding::sysname)]


you should only get the ones below the current context node.
The problem is that preceding also checks the ones before the context node!
Maybe you can work with variables and store all already handled sysnames in a variable and query on them.

hth

5

Wednesday, November 1st 2006, 9:06am

sorry for the late reply. I already thought about the variable method. But the problem was, that as soon as a variable got created, I cannot change the value anymore. The only thing I want to achieve is to check, wheter the current node/element/value within the "xsl:for-each" block is the same one as the last one (after the nodes have been sorted). Therefore I wanted to store the current one in a variable and compare it during the next loop. Not XSL but how I would do it in any other programming language:

lastValue=""
for each <value> in <array>
if (<lastValue> == <value>) continue;
lastValue=<value>
doWhatYouWantToDo
next

I was not able to achieve this within XSL.

The method using "preceding" basically works but I also have to pay attention to the fact, that the list is not sorted in the XML. Concerning the sample from the intial question, I would get a list like:

ABC
ABC
XYZ
DEF
ABC
XYZ
DEF

After sorting I would get:

ABC
ABC
ABC
DEF
DEF
XYZ
XYZ

This has to be reduced to:

ABC
DEF
XYZ

still thinking of the context (based on the <record> tag).

Any additional comment / input would be highly appreciated espacially concerning the variable part. So your suggestion on collecting the values in a variable and query for them ... how would you handle that?

Thanky you!

6

Thursday, November 2nd 2006, 1:24pm

Good question :)
Unfortunately I'm not so deep in the XSLT thing but I will see what I can do for you.
Maybe you can send the full XML file via a personal message (ZIP, RAR, etc.) so I can check it out.

regards

Similar threads

Social bookmarks