1
votes

I'm working with an XSLT that someone else designed. Back in that time the xml structure was like this:

<errata_section id="i875" errata_type="bug">
<title>Title</title>
<description>
    <para> Following a warm Reset  </para>
</description>
<devices_impacted>
    <device_name>VAZER</device_name>
</devices_impacted>
<devices_impacted>
    <device_name>VAZER2</device_name>
</devices_impacted>
<devices_impacted>
    <device_name>VAZER3</device_name>
</devices_impacted>
<module_impacted>Boot</module_impacted>
</errata_section>

The existing transform defines a key as follows:

<xsl:key name="module-index" match="errata_section" use="module_impacted"/>

And they have used the following Muenchian Method algorithm to go through all the unique module_impacted elements.

 <xsl:element name="article">
 <xsl:attribute name="id">errata_module_impacted</xsl:attribute>
 <xsl:attribute name="arch"><xsl:value-of select='$device_to_output'/></xsl:attribute>
 <title>Modules Impacted</title>



<xsl:for-each select="//errata_section[generate-id(.)=generate-id(key('module-index', module_impacted)[1])]">
 <xsl:sort select="module_impacted"/>

    <xsl:variable name="current_module"><xsl:value-of select="module_impacted"/></xsl:variable> 

    <!-- The is an ugly kludge: we check each section that applies to current chip and update the "is_module_in_device" 
        variable if we find it impacts the current module. In the end we have either an empty variable (module is not in 
        current device) or a variable containing lots of "o" concatenated (module is in current device) -->

    <xsl:variable name="is_module_in_device">
      <xsl:for-each select="//errata_section/devices_impacted[device_name=$device_to_output]">
    <xsl:choose>

        <!-- Start Patch part 1(removing modules having and arch attribute containing a different than the current device) -->

        <xsl:when test='../module_impacted[@arch!=$device_to_output]'>
           <xsl:if test='../module_impacted/text()[.=$current_module]'>
        <xsl:text></xsl:text>
       </xsl:if>
        </xsl:when>
    <!-- End Patch part 1 -->

        <xsl:otherwise>
       <xsl:if test='../module_impacted/text()[.=$current_module]'><xsl:text>o</xsl:text></xsl:if>
        </xsl:otherwise>
    </xsl:choose>
      </xsl:for-each>
    </xsl:variable>

<!-- Start Patch part 2 -->
<xsl:variable name="removed_modules">
      <xsl:for-each select="//errata_section/devices_impacted[device_name=$device_to_output]">
    <xsl:choose>
        <xsl:when test='../module_impacted[@arch!=$device_to_output]'>
           <xsl:if test='../module_impacted/text()[.=$current_module]'>
        <xsl:text>r</xsl:text>
       </xsl:if>
        </xsl:when>
    </xsl:choose>
      </xsl:for-each>
    </xsl:variable>    
<!-- End Patch part 2 -->

    <xsl:if test='contains($is_module_in_device, "o")'>
      <xsl:variable name="num_of_sections">
    <!-- Start Patch part 3 -->
        <xsl:value-of select="string-length($is_module_in_device) + string-length($removed_modules)"/>
    <!-- End Patch part 3 -->
      </xsl:variable>

<!-- Do Table processing -->

<table>
       <xsl:attribute name="id">
       <xsl:value-of select="generate-id(module_impacted)"/>
       </xsl:attribute>
        <title>
        <xsl:text>Module </xsl:text>
        <xsl:value-of select="module_impacted"/>
        <xsl:text> (</xsl:text><xsl:value-of select="$num_of_sections"/><xsl:text> section</xsl:text>
        <!-- a little grammar ! -->
        <xsl:if test="$num_of_sections&gt;1"><xsl:text>s</xsl:text></xsl:if><xsl:text>)</xsl:text>
        </title>
        <xsl:element name="tgroup"> 
        <xsl:attribute name="cols">
        <xsl:choose>
          <xsl:when test='$show_review="yes"'>3</xsl:when>
          <xsl:otherwise>2</xsl:otherwise>
        </xsl:choose>
        </xsl:attribute>
        <xsl:element name="colspec"> 
          <xsl:attribute name="colwidth">1*</xsl:attribute>
          <xsl:attribute name="colname">_1</xsl:attribute>
        </xsl:element>
        <xsl:choose>
            <xsl:when test='$show_review="yes"'>
               <xsl:element name="colspec">
                  <xsl:attribute name="colwidth">6*</xsl:attribute>
                  <xsl:attribute name="colname">_2</xsl:attribute>
               </xsl:element>
               <xsl:element name="colspec">
                  <xsl:attribute name="colwidth">1*</xsl:attribute>
                  <xsl:attribute name="colname">_3</xsl:attribute>
               </xsl:element>
            </xsl:when>
            <xsl:otherwise>
               <xsl:element name="colspec">
                  <xsl:attribute name="colwidth">8*</xsl:attribute>
                  <xsl:attribute name="colname">_2</xsl:attribute>
               </xsl:element>
            </xsl:otherwise>
        </xsl:choose>
        <tbody>
        <row>
        <xsl:element name="entry">
        <xsl:attribute name="morerows"><xsl:value-of select="$num_of_sections"/></xsl:attribute>
        <xsl:value-of select="module_impacted"/>
        </xsl:element>
        <entry>Section</entry>
        <xsl:if test='$show_review="yes"'>
        <entry>Review status</entry>
        </xsl:if>
        </row>
          <xsl:for-each select="key('module-index', module_impacted)">
            <xsl:if test="devices_impacted/device_name/text()[.=$device_to_output]">
              <row>
              <entry>
      <xsl:value-of select="@id" /><xsl:text>: </xsl:text>
              <xsl:element name="xref">
              <xsl:attribute name="linkend">sect_<xsl:value-of select="@id" />
              </xsl:attribute></xsl:element>
              </entry>
              <xsl:if test='$show_review="yes"'>
              <entry>
              <xsl:value-of select="review_status" />
              </entry>
              </xsl:if>
               </row>
            </xsl:if>                       
          </xsl:for-each>
        </tbody>
      </xsl:element> <!-- tgroup element -->
    </table>
    </xsl:if>
    </xsl:for-each>
  </xsl:element>

Everything worked fine as long as there was only one module_impacted element per errata_section. As time has went on, more than one module_impacted element has been assigned to a given errata_section, so that you may have errata_section elements like the following:

<errata_section id="i876" errata_type="bug">
<title>Title</title>
<description>
    <para> Following a warm Reset  </para>
</description>
<devices_impacted>
    <device_name>VAZER</device_name>
</devices_impacted>
<devices_impacted>
    <device_name>VAZER2</device_name>
</devices_impacted>
<devices_impacted>
    <device_name>VAZER3</device_name>
</devices_impacted>
<module_impacted>Boot</module_impacted>
<module_impacted>Power-On</module_impacted>
<module_impacted>DMA</module_impacted>
</errata_section>

The source xml has close to a thousand of these errata_section bugs, and they use the Muenchian Method to determine unique module_impacted nodes, that are then put into a table, pointing back to the relevant errata_section element. Since we have gone to the multiple module_impacted elements per errata_section things have been quirky, and some module_impacted elements have not been caught.

My question to the XSLT experts out there, is this because as the Muenchian Method is comparing the id of the errata_section element containing module_impacted element(s), it may find the first module_impacted element but not necessarily any sibling module_impacted elements? What should happen in a situation like this, where you have more than one of a given element that you are comparing id's for?

I'm just trying to get my head around this, and try and find a better solution!

Thanks for any help!

Revision:

I am hesitant to put more of the code that is occurring inside the for-each statement, as it was written by someone else, is quite kludgey, and I'm not really sure what is going on, but i have updated the above code to show more of that! Inside the for-each loop, they are trying, i think, to keep track and determine the number of times a given module_impacted element has occurred. Then, it will generate a table structure that contains the name of the module impacted, how many occurrences, and entry elements containing the relevant errata_section element.

As per Martin's suggestion, I changed my code but am still not getting all the module_impacted groups per a given device_impacted/device_name. The code below is what i currently running:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:param name="show_review">no</xsl:param>

<xsl:key name="module-index" match="errata_section" use="module_impacted"/>

<xsl:key name="group" match="errata_section/module_impacted" use="."/>
<xsl:key name="device-index" match="errata_section" use="devices_impacted/device_name"/>

<xsl:template name="table_of_section_per_module">
  <xsl:param name="device_to_output"/>
  <xsl:element name="article">
      <xsl:attribute name="id">errata_module_impacted</xsl:attribute>
      <xsl:attribute name="arch"><xsl:value-of select='$device_to_output'/></xsl:attribute>
      <title>Modules Impacted</title>

      <xsl:for-each select="//errata_section[devices_impacted/device_name=$device_to_output]/module_impacted[generate-id() = generate-id(key('group', .)[1])]">
          <xsl:sort select="."/>
          <xsl:variable name="current-group" select="key('module-index', .)"/>
          <xsl:variable name="current-grouping-key" select="."/>
          <xsl:variable name="num_of_sections" select="count(.)"/>
          <xsl:variable name="current_module" select="."/>

          <table>
              <xsl:attribute name="id">
                  <xsl:value-of select="generate-id($current-grouping-key)"/>
              </xsl:attribute>
              <title>
                  <xsl:text>Module </xsl:text>
                  <xsl:value-of select="$current_module"/>
                  <xsl:text> (</xsl:text><xsl:value-of select="$num_of_sections"/><xsl:text> section</xsl:text>
                  <!-- a little grammar ! -->
                  <xsl:if test="$num_of_sections&gt;1"><xsl:text>s</xsl:text></xsl:if><xsl:text>)</xsl:text>
              </title>
              <xsl:element name="tgroup"> 
                  <xsl:attribute name="cols">
                      <xsl:choose>
                          <xsl:when test='$show_review="yes"'>3</xsl:when>
                          <xsl:otherwise>2</xsl:otherwise>
                      </xsl:choose>
                  </xsl:attribute>
                  <xsl:element name="colspec"> 
                      <xsl:attribute name="colwidth">1*</xsl:attribute>
                      <xsl:attribute name="colname">_1</xsl:attribute>
                  </xsl:element>
                  <xsl:choose>
                      <xsl:when test='$show_review="yes"'>
                          <xsl:element name="colspec">
                              <xsl:attribute name="colwidth">6*</xsl:attribute>
                              <xsl:attribute name="colname">_2</xsl:attribute>
                          </xsl:element>
                          <xsl:element name="colspec">
                              <xsl:attribute name="colwidth">1*</xsl:attribute>
                              <xsl:attribute name="colname">_3</xsl:attribute>
                          </xsl:element>
                      </xsl:when>
                      <xsl:otherwise>
                          <xsl:element name="colspec">
                              <xsl:attribute name="colwidth">8*</xsl:attribute>
                              <xsl:attribute name="colname">_2</xsl:attribute>
                          </xsl:element>
                      </xsl:otherwise>
                  </xsl:choose>
                  <tbody>
                      <row>
                          <xsl:element name="entry">
                              <xsl:attribute name="morerows"> <xsl:value-of select="$num_of_sections"/></xsl:attribute>
                              <xsl:value-of select="$current_module"/>
                          </xsl:element>
                          <entry>Section</entry>
                          <xsl:if test='$show_review="yes"'>
                              <entry>Review status</entry>
                          </xsl:if>
                      </row>
                      <xsl:for-each select="$current-group">
                          <row>
                              <entry>
                                  <xsl:value-of select="@id" /><xsl:text>: </xsl:text>
                                  <xsl:element name="xref">
                                      <xsl:attribute name="linkend">sect_<xsl:value-of select="@id" />
                                  </xsl:attribute></xsl:element>
                              </entry>
                              <xsl:if test='$show_review="yes"'>
                                  <entry>
                                      <xsl:value-of select="review_status" />
                                  </entry>
                              </xsl:if>
                          </row>
                      </xsl:for-each>
                  </tbody>
              </xsl:element> <!-- tgroup element -->
          </table>
      </xsl:for-each>
  </xsl:element>
</xsl:template>
<xsl:template match="/">
 <book>
   <xsl:call-template name="table_of_section_per_module">
       <xsl:with-param name="device_to_output">VAZER</xsl:with-param>
   </xsl:call-template>
   </book>
   </xsl:template>
 </xsl:stylesheet>

THANKS for everyone help on this!!! Russ

1
Hmm. I could certainly speak to more efficient implementations using an indexed XQuery database, but I don't know whether / to what extent XSLT promises any performance characteristics.Charles Duffy
The key and the Muenchian grouping should work with multiple elements. However, the sort you have shown would not sort on the keyed value but on the first element. And you have not shown us the code inside of the for-each at all.Martin Honnen
@RussUrquhart the for-each construct you've is going to iterate on the first errata_section with the specified module_impacted in the whole document. But as Martin Honnen suggests, you should show the further code to let us know what it does exactly. And btw, Muenchian's grouping is a very efficient solution to grouping.Lingamurthy CS

1 Answers

2
votes

What you could try is replace the code

<xsl:key name="module-index" match="errata_section" use="module_impacted"/>

...

<xsl:for-each select="//errata_section[generate-id(.)=generate-id(key('module-index', module_impacted)[1])]">
 <xsl:sort select="module_impacted"/>

<!-- Do some processing -->

</xsl:for-each>

with

<xsl:key name="module-index" match="errata_section" use="module_impacted"/>
<xsl:key name="group" match="errata_section/module_impacted" use="."/>

...

<xsl:for-each select="//errata_section/module_impacted[generate-id() = generate-id(key('group', .)[1])]">">
 <xsl:sort select="."/>
 <xsl:variable name="current-group" select="key('module-index', .)"/>
 <xsl:variable name="current-grouping-key" select="."/>

<!-- Do some processing -->

</xsl:for-each>

then you should be able to use those variables to output the grouping key respectively the errata_section elements in each group. The rest depends on the <!-- Do some processing --> stuff you have now shown, I think you need to replace

<xsl:for-each select="//errata_section[generate-id(.)=generate-id(key('module-index', module_impacted)[1])]">
 <xsl:sort select="module_impacted"/>

    <xsl:variable name="current_module"><xsl:value-of select="module_impacted"/></xsl:variable> 

with (using the two key definitions already posted)

<xsl:for-each select="//errata_section/module_impacted[generate-id() = generate-id(key('group', .)[1])]">">
 <xsl:sort select="."/>
 <xsl:variable name="current-group" select="key('module-index', .)"/>
 <xsl:variable name="current_module" select="."/>

then later one code like

<xsl:for-each select="key('module-index', module_impacted)">

needs to be replaced with

<xsl:for-each select="$current-group">

and any reference doing <xsl:value-of select="module_impacted"/> needs to be <xsl:value-of select="$current_module"/>.

As far as I can tell my suggestions should work to get the grouping right, I have not tried to understand and improve the other content of the for-each. Here is the sample I used:

<article>

  <errata_section id="e1" errata_type="bug">
    <title>e1</title>
    <module_impacted>m1</module_impacted>
    <module_impacted>m3</module_impacted>
  </errata_section>

  <errata_section id="e2" errata_type="bug">
    <title>e2</title>
    <module_impacted>m1</module_impacted>
    <module_impacted>m2</module_impacted>
  </errata_section>

  <errata_section id="e3" errata_type="bug">
    <title>e3</title>
    <module_impacted>m1</module_impacted>
    <module_impacted>m3</module_impacted>
  </errata_section>

</article>

Now when grouping that with your original code

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="module-index" match="errata_section" use="module_impacted"/>

<xsl:template match="/">
  <xsl:for-each select="//errata_section[generate-id(.)=generate-id(key('module-index', module_impacted)[1])]">
    <xsl:sort select="module_impacted"/>
    <group key="{module_impacted}">
      <xsl:copy-of select="key('module-index', module_impacted)"/>
    </group>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

we only find one group:

<group key="m1">
   <errata_section id="e1" errata_type="bug">
      <title>e1</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m3</module_impacted>
   </errata_section>
   <errata_section id="e2" errata_type="bug">
      <title>e2</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m2</module_impacted>
   </errata_section>
   <errata_section id="e3" errata_type="bug">
      <title>e3</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m3</module_impacted>
   </errata_section>
</group>

Using my suggestion

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="module-index" match="errata_section" use="module_impacted"/>
<xsl:key name="group" match="errata_section/module_impacted" use="."/>

<xsl:template match="/">
  <xsl:for-each select="//errata_section/module_impacted[generate-id() = generate-id(key('group', .)[1])]">
    <xsl:sort select="."/>
    <xsl:variable name="current-group" select="key('module-index', .)"/>
    <xsl:variable name="current_module" select="."/>
    <group key="{$current_module}">
      <xsl:copy-of select="$current-group"/>
    </group>
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

the result is

<group key="m1">
   <errata_section id="e1" errata_type="bug">
      <title>e1</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m3</module_impacted>
   </errata_section>
   <errata_section id="e2" errata_type="bug">
      <title>e2</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m2</module_impacted>
   </errata_section>
   <errata_section id="e3" errata_type="bug">
      <title>e3</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m3</module_impacted>
   </errata_section>
</group>
<group key="m2">
   <errata_section id="e2" errata_type="bug">
      <title>e2</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m2</module_impacted>
   </errata_section>
</group>
<group key="m3">
   <errata_section id="e1" errata_type="bug">
      <title>e1</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m3</module_impacted>
   </errata_section>
   <errata_section id="e3" errata_type="bug">
      <title>e3</title>
      <module_impacted>m1</module_impacted>
      <module_impacted>m3</module_impacted>
   </errata_section>
</group>

Based on that I think my suggestion should help you fix the grouping of your elements where you have multiple module_impacted items to group on.

As for your latest edit, I think it is better to first filter the elements into a result tree fragment, convert that into a node-set using exsl:node-set or similar and then group using Muenchian grouping, as suggested earlier. I would do that along the following lines:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="exsl">

<xsl:output indent="yes" />
<xsl:strip-space elements="*"/>

<xsl:param name="dv" select="'VAZER'"/>

<xsl:key name="by-dv" match="errata_section" use="devices_impacted/device_name"/>

<xsl:variable name="err-rtf">
  <xsl:copy-of select="key('by-dv', $dv)"/>
</xsl:variable>

<xsl:variable name="err" select="exsl:node-set($err-rtf)"/>

<xsl:param name="show_review">no</xsl:param>

<xsl:key name="module-index" match="errata_section" use="module_impacted"/>

<xsl:key name="group" match="errata_section/module_impacted" use="."/>
<xsl:key name="device-index" match="errata_section" use="devices_impacted/device_name"/>

<xsl:template name="table_of_section_per_module">
  <xsl:element name="article">
      <xsl:attribute name="id">errata_module_impacted</xsl:attribute>
      <xsl:attribute name="arch"><xsl:value-of select='$dv'/></xsl:attribute>
      <title>Modules Impacted</title>

      <xsl:for-each select="$err//module_impacted[generate-id() = generate-id(key('group', .)[1])]">
          <xsl:sort select="."/>
          <xsl:variable name="current-group" select="key('module-index', .)"/>
          <xsl:variable name="current-grouping-key" select="."/>
          <xsl:variable name="num_of_sections" select="count(.)"/>
          <xsl:variable name="current_module" select="."/>

          <table>
              <xsl:attribute name="id">
                  <xsl:value-of select="generate-id($current-grouping-key)"/>
              </xsl:attribute>
              <title>
                  <xsl:text>Module </xsl:text>
                  <xsl:value-of select="$current_module"/>
                  <xsl:text> (</xsl:text><xsl:value-of select="$num_of_sections"/><xsl:text> section</xsl:text>
                  <!-- a little grammar ! -->
                  <xsl:if test="$num_of_sections&gt;1"><xsl:text>s</xsl:text></xsl:if><xsl:text>)</xsl:text>
              </title>
              <xsl:element name="tgroup"> 
                  <xsl:attribute name="cols">
                      <xsl:choose>
                          <xsl:when test='$show_review="yes"'>3</xsl:when>
                          <xsl:otherwise>2</xsl:otherwise>
                      </xsl:choose>
                  </xsl:attribute>
                  <xsl:element name="colspec"> 
                      <xsl:attribute name="colwidth">1*</xsl:attribute>
                      <xsl:attribute name="colname">_1</xsl:attribute>
                  </xsl:element>
                  <xsl:choose>
                      <xsl:when test='$show_review="yes"'>
                          <xsl:element name="colspec">
                              <xsl:attribute name="colwidth">6*</xsl:attribute>
                              <xsl:attribute name="colname">_2</xsl:attribute>
                          </xsl:element>
                          <xsl:element name="colspec">
                              <xsl:attribute name="colwidth">1*</xsl:attribute>
                              <xsl:attribute name="colname">_3</xsl:attribute>
                          </xsl:element>
                      </xsl:when>
                      <xsl:otherwise>
                          <xsl:element name="colspec">
                              <xsl:attribute name="colwidth">8*</xsl:attribute>
                              <xsl:attribute name="colname">_2</xsl:attribute>
                          </xsl:element>
                      </xsl:otherwise>
                  </xsl:choose>
                  <tbody>
                      <row>
                          <xsl:element name="entry">
                              <xsl:attribute name="morerows"> <xsl:value-of select="$num_of_sections"/></xsl:attribute>
                              <xsl:value-of select="$current_module"/>
                          </xsl:element>
                          <entry>Section</entry>
                          <xsl:if test='$show_review="yes"'>
                              <entry>Review status</entry>
                          </xsl:if>
                      </row>
                      <xsl:for-each select="$current-group">
                          <row>
                              <entry>
                                  <xsl:value-of select="@id" /><xsl:text>: </xsl:text>
                                  <xsl:element name="xref">
                                      <xsl:attribute name="linkend">sect_<xsl:value-of select="@id" />
                                  </xsl:attribute></xsl:element>
                              </entry>
                              <xsl:if test='$show_review="yes"'>
                                  <entry>
                                      <xsl:value-of select="review_status" />
                                  </entry>
                              </xsl:if>
                          </row>
                      </xsl:for-each>
                  </tbody>
              </xsl:element> <!-- tgroup element -->
          </table>
      </xsl:for-each>
  </xsl:element>
</xsl:template>
<xsl:template match="/">
 <book>
   <xsl:call-template name="table_of_section_per_module"/>
   </book>
   </xsl:template>
 </xsl:stylesheet>

Untested as you have not provided much of an input sample and wanted result to test with, but should get you an idea.