Walking / looping through an XSL key: how?

Is there a way to go through the key and output all the values ​​it contains?

   <xsl:key name="kElement" match="Element/Element[@idref]" use="@idref" />

      

I thought so:

<xsl:for-each select="key('kElement', '.')">
  <li><xsl:value-of select="." /></li>
</xsl:for-each>

      

However, this doesn't work. I just want to list all the values ​​in a key for testing purposes.

The question is simple: how can this be done?

+2


a source to share


4 answers


You can not. You don't need keys for this.

You can scroll through each item in a key with a single click on key()

if and only if the key of each item is the same.

If you need to iterate over everything defined for a key, you can use an expression in an attribute of match="..."

your element <key>

.

So, if you have a file like this:

<root>
  <element name="Bill"/>
  <element name="Francis"/>
  <element name="Louis"/>
  <element name="Zoey"/>
</root>

      

And the key is defined like this:

<xsl:key name="survivors" match="element" use="@name"/>

      

You can skip what the key is using by using its attribute match

:

<xsl:for-each select="element">
  <!-- stuff -->
</xsl:for-each>

      

Alternatively, if each item has something in common:



<root>
  <element name="Bill" class="survivor"/>
  <element name="Francis" class="survivor"/>
  <element name="Louis" class="survivor"/>
  <element name="Zoey" class="survivor"/>
</root>

      

Then you can define your key like this:

<xsl:key name="survivors" match="element" use="@class"/>

      

And iterate over all the elements as follows:

<xsl:for-each select="key('survivors', 'survivor')">
  <!-- stuff -->
</xsl:for-each>

      

As each element shares the "survivor" value for the attribute class

.

In your case, your key

<xsl:key name="kElement" match="Element/Element[@idref]" use="@idref" />

      

So, you can view whatever you want:

<xsl:for-each select="Element/Element[@idref]">
  <!-- stuff -->
</xsl:for-each>

      

+13


a source


You can create a key to use for looping - if you just specify a constant in the use attribute of the key element:

<xsl:key name="survivors" match="element" use="'all'"/>

      

Then you can iterate over all the elements like this:

<xsl:for-each select="key('survivors','all')">
    ...
</xsl:for-each>

      

Or count them:

<xsl:value-of select="count(key('survivors','all'))"/>

      



Note that the constant can be any string, or even a number - but "everything" reads well.

However, you cannot use this key to find information about individual records (since they all have the same key).

In other words, there are two types of possible keys:

  • "search keys" = standard keys with different indexes in the use attribute
  • "looping keys" = keys with a constant in the use attribute

I don't know how efficient this technique is to execute, however it makes serving the XSL more efficient by avoiding repeating the same (potentially very complex) XPath expression throughout the XSL code.

+4


a source


Instead of thinking of XSL keys in terms of a programming language, think of them as SQL recordsets. This will give a better understanding. For a given key index, created as

<xsl:key name="paths" match="path" use="keygenerator()">

      

it can be "iterated" / "walked through" as shown below

<xsl:for-each select="//path[generate-id()=generate-id(key('paths',keygenerator())[1])]">

      

To understand this magic number [1]

, let s consider the example below:

Consider this XML snippet

<root>
    <Person>
        <name>Johny</name>
        <date>Jan10</date>
        <cost itemID="1">34</cost>
        <cost itemID="1">35</cost>
        <cost itemID="2">12</cost>
        <cost itemID="3">09</cost>
    </Person>
    <Person>
        <name>Johny</name>
        <date>Jan09</date>
        <cost itemID="1">21</cost>
        <cost itemID="1">41</cost>
        <cost itemID="2">11</cost>
        <cost itemID="2">14</cost>
    </Person>
</root>

      

transformed using this XSL.

  <xsl:for-each select="*/Person">
  <personrecords>
       <xsl:value-of select="generate-id(.)" />--
       <xsl:value-of select="name"/>--
       <xsl:value-of select="date"/>--      
  </personrecords>
  </xsl:for-each>

  <xsl:for-each select="*/*/cost">
  <costrecords>
      <xsl:value-of select="generate-id(.)" />--
      <xsl:value-of select="../name"/>-- 
      <xsl:value-of select="../date"/>-- 
      <xsl:value-of select="@itemID"/>-- 
      <xsl:value-of select="text()"/>
  </costrecords>
  </xsl:for-each>

      

The above XSL transformation lists the unique identifier of nodes Person

and nodes cost

in the form idpxxxxxxx

as shown below.

  1. <personrecords>idp2661952--Johny--Jan10--      </personrecords>
  2. <personrecords>idp4012736--Johny--Jan09--      </personrecords>

  3. <costrecords>idp2805696--Johny-- Jan10-- 1-- 34</costrecords>
  4. <costrecords>idp4013568--Johny-- Jan10-- 1-- 35</costrecords>
  5. <costrecords>idp2808192--Johny-- Jan10-- 2-- 12</costrecords>
  6. <costrecords>idp2808640--Johny-- Jan10-- 3-- 09</costrecords>
  7. <costrecords>idp2609728--Johny-- Jan09-- 1-- 21</costrecords>
  8. <costrecords>idp4011648--Johny-- Jan09-- 1-- 41</costrecords>
  9. <costrecords>idp2612224--Johny-- Jan09-- 2-- 11</costrecords>
  10.<costrecords>idp2610432--Johny-- Jan09-- 2-- 14</costrecords>

      

Let's create a key in records cost

using a combination of values name

and itemID

.

 <xsl:key name="keyByNameItem" match="cost" use="concat(../name, '+', @itemID)"/>

      

In a manual XML search, the number of unique keys for the above would be three: Johny + 1 , Johny + 2, and Johny + 3 .

Now let's test this key using the snippet below.

   <xsl:for-each select="*/*/cost">
   <costkeygroup>
      <xsl:value-of select="generate-id(.)" />-- 
      (1)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', @itemID) )[1] ) " />-- 
      (2)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', @itemID) )[2] ) " />-- 
      (3)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', @itemID) )[3] ) " />-- 
      (4)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', @itemID) )[4] ) " />
    </costkeygroup>
    </xsl:for-each>

      

And here's the result:

  1. <costkeygroup>idp2805696-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
  2. <costkeygroup>idp4013568-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
  3. <costkeygroup>idp2808192-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
  4. <costkeygroup>idp2808640-- (1)idp2808640-- (2)-- (3)-- (4)</costkeygroup>
  5. <costkeygroup>idp2609728-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
  6. <costkeygroup>idp4011648-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
  7. <costkeygroup>idp2612224-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
  8. <costkeygroup>idp2610432-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>

      

Our interest is to try to understand the importance of [1]

, [2]

, [3]

, [4]

. In our case, the key generator concat(../name, '+', @itemID)

.

For a given key, [1]

refers to the first node event that satisfies the key generator. Similarly [2]

applies to the second occurrence of the node that satisfies the key generator. Thus [2]

, [3]

, [4]

etc. - all nodes that match the same key and thus can be considered duplicates for a given key. The number of duplicates depends on the input XML. In this way:

Key Johny + 1 satisfies 4 nodes (1) idp2805696-- (2) idp4013568-- (3) idp2609728-- (4) idp4011648
  Key Johny + 2 satisfies 3 nodes (1) idp2808192-- (2) idp2612224-- (3 ) idp2610432-- (4)
  Key Johny + 3 satisfies 1 node (1) idp2808640-- (2) - (3) - (4)

So we can see that ALL 8cost

XML nodes can be accessed via a key.

Here is an image that combines the conversion results for a better understanding.

enter image description here

The red squares indicate the matching nodes for Johny + 1 . Green squares indicate matching nodes for Johny + 3 . Match the values idpxxxxxxx

in <costkeygroup>

with the values ​​in <costrecords>

. <costrecords>

help map values idpxxxxxxx

to the original XML.

The conclusion is that

  

The XSL key does not filter or remove nodes. All nodes, including duplicates, can be accessed through a key. Thus, when we say "traverse" the key, there is no concept of the resulting subset of nodes from the original set of nodes available for the key to process.

  

To "skip" only unique key nodes in the above example, use

 <xsl:for-each select="*/*/workTime[generate-id()=generate-id(key('keyByNameItem', concat(../name, '+', @itemID) )[1] ) ] ">

      

[1]

means the first record for a given key value is designated as a unique record. [1]

is almost always used because there will be at least one node that matches the given key value. If we are confident that there will be at least 2 records for each key value in the key, we can proceed and use [2]

to identify the second record in the record set as a unique record.

PS The words nodes / records / elements are used interchangeably.

+2


a source


There is no way to go through the keys, although we can output all the values ​​it contains. In XSLT2 it is much simpler than in XSLT1 (for example using fn:generate-id

as per the previous answer).

Using fn:distinct-values

<xsl:variable name="e" select="."/>
<xsl:for-each select="distinct-values(Element/Element[@idref]/@idref)">
  <li key="{.}"><xsl:value-of select="key('kElement', ., $e )" /></li>
</xsl:for-each>

      

Using xsl:for-each-group

<xsl:for-each-group select="Element/Element[@idref]" group-by="@idref">
  <li key="{current-grouping-key()}"><xsl:value-of select="current-group()" /></li>
</xsl:for-each-group>

      

0


a source







All Articles