0

I read expressions like this

<xsl:variable name="myVar" select="$data[not(key('myKey',@myRef))]"/>

in legacy code. Most likeley it is code from experts ;-). I'm wondering what it does, how it works and how i could reeng it in order to make it more readable. Thank you.

  • 1
    @Tomalak Hello Tomalak. You previously answered, corrected, commented (multiple times) and finally deleted your important post and answer [Screenshot](https://www.magentacloud.de/lnk/qciihZMT). There was much important information in the comments part written from others and links to fiddle. It is lost now. Why did you remove your answer? :-( –  Feb 06 '20 at 05:06
  • I assume he deleted it because the number of comments went way off the rails, and more unrelated questions were asked there. If you have more questions, click the "Ask Question" button to ask a new question. Stackoverflow should not be used like a discussion forum. – Thomas W Feb 06 '20 at 06:30

2 Answers2

0

Keys are an important aspect of XSLT. Instead of re-engineering them, it's better to learn the concept.

Keys can be understood as tables with nodes stored under specific keys. They are defined like this:

<xsl:key name="addressByStreet" match="address" use="street"/>

The name attribute is just a QName (similar to a variable name). The match attribute holds an XPath expression that works similarly to the match attribute of <xsl:template>. When the processor finds a node that matches the expression, it evaluates the XPath expression of the use attribute in the context of the matched element. If this expression returns values, they will be used to create new entries in the "key table" for the matched element.

To illustrate that: The above key creates a table with all the <address> elements in the processed document, keyed by the value of their <street> child. This means, if you have these elements:

<address>
  <street>Main Street</street>
  <number>123</number>
</address>
<address>
  <street>Main Street</street>
  <number>456</number>
</address>
<address>
  <street>Country Road</street>
  <street>Country Rd.</street>
  <number>789</number>
</address>

… you could then use key('addressByStreet', 'Main Street') to retrieve all the listed addresses in Main Street.

You can use both key('addressByStreet', 'Country Road') and key('addressByStreet', 'Country Rd.') to retrieve the last address.

Why use keys here? The above expression could be re-implemented like //address[street='Main Street'], but now every time this expression is called, the XSLT processor likely goes through the entire document again. That's a problem if a template or loop is called often. Keys can have huge performance benefits (e.g. reduce complexity from O(n²) to O(n)) because the results are "cached".

There are many applications and patterns in which keys are used. For example if you have this XML:

<street-list>
  <street>Main Street</street>
  <street>Bumpy Road</street>
</street-list>

The expression street-list/street[not(key('addressByStreet', .))] will filter the list of streets and only return streets for which there is no address in the above list – i.e. only "Bumpy Road" in this case because for "Main Street", a key entry exists.

A typical application of keys in XSLT 1 is Muenchian grouping.

Thomas W
  • 14,757
  • 6
  • 48
  • 67
0

I've got the use case now. No, it is not legacy code. This is clear from context and definition of key and data.

If we have data like this:

<xsl:variable name="dict">
  <ITEMS>
    <ITEM id="1" content="it1">
      <ITEM-REF ref="3"/>
    </ITEM>
    <ITEM id="2" content="it2">
      <ITEM-REF ref="1"/>
    </ITEM>
    <ITEM id="3" content="it3">
      <ITEM-REF ref="6"/>
    </ITEM>
    <ITEM id="4" content="it4">
      <ITEM-REF ref="3"/>
    </ITEM>
    <ITEM id="5" content="it5">
      <ITEM-REF ref="5"/>
    </ITEM>
    <ITEM id="6" content="it6">
      <ITEM-REF ref="8"/>
    </ITEM>
    <ITEM id="7" content="it7">
      <ITEM-REF ref="9"/>
    </ITEM>
  </ITEMS>
</xsl:variable>

And we want to get all ITEM-REF elements with @ref values where there is no ITEM with the same @id value (broken links) the expression can help out:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:output method="xml" encoding="utf-8" indent="yes"/>

    <xsl:variable name="dict">
      <ITEMS>
        <ITEM id="1" content="it1">
          <ITEM-REF ref="3"/>
        </ITEM>
        <ITEM id="2" content="it2">
          <ITEM-REF ref="1"/>
        </ITEM>
        <ITEM id="3" content="it3">
          <ITEM-REF ref="6"/>
        </ITEM>
        <ITEM id="4" content="it4">
          <ITEM-REF ref="3"/>
        </ITEM>
        <ITEM id="5" content="it5">
          <ITEM-REF ref="5"/>
        </ITEM>
        <ITEM id="6" content="it6">
          <ITEM-REF ref="8"/>
        </ITEM>
        <ITEM id="7" content="it7">
          <ITEM-REF ref="9"/>
        </ITEM>
      </ITEMS>
    </xsl:variable>

    <xsl:key name="itemkey" match="ITEM" use="@id"/>

    <xsl:template match="START">
      <xsl:variable name="allItems" select="msxsl:node-set($dict)//ITEM"/>
      <xsl:variable name="allItemRefs" select="msxsl:node-set($dict)//ITEM-REF"/>
      <xsl:variable name="itemRefsNotReferencingOtherItems" select="$allItemRefs[not(key('itemkey',@ref))]"/>
      <REFERENCED-NOT-EXISTING>
        <xsl:for-each select="msxsl:node-set($itemRefsNotReferencingOtherItems)">
          <ITEM>
            <xsl:attribute name="id">
              <xsl:value-of select="@ref"/>
            </xsl:attribute>
          </ITEM>
        </xsl:for-each>
      </REFERENCED-NOT-EXISTING>
    </xsl:template>

  </xsl:stylesheet>

Output:

<?xml version="1.0" encoding="utf-8"?>
<REFERENCED-NOT-EXISTING>
  <ITEM id="8" />
  <ITEM id="9" />
</REFERENCED-NOT-EXISTING>

Input file:

<?xml version="1.0" encoding="utf-8"?>
<START/>