xsl-list
[Top] [All Lists]

[xsl] Grouping

2020-10-21 10:25:24
Hello xsl-list,

the question of Charlie brought me to thinking about a problem and solution
that I came up with a few years ago.
A few words upfront:
- This is a _very_ stripped down example which doesn't reflect the complete
structure. I hope that I was able to retain all relevant structural
attributes that shape the solution.
- I included <parentA> and <parentB> in the example to account for the fact
that there are at least 20 different parents where the relevant <points>
can occur as childs. It's also possible that a parent holds relevant
<points>, completely different nodes and also another parent.
- I don't want to include <xsl:for-each-group > in every possible parent
for the sake of maintainability. Because of the loads of different child
nodes it seems to me atm that it's not sensible to write specialized
select-statements in apply-templates. Just select="@* | node()" and let the
child-nodes "somehow" take care of themselves.

My solution is somewhat like the following
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="
http://www.w3.org/1999/XSL/Transform";>
  <xsl:output indent="yes" method="xml" encoding="UTF-8"
media-type="text/xml"/>

  <xsl:template match="parentA">
    <xsl:copy>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="parentB">
    <xsl:copy>
      <xsl:apply-templates/>
    </xsl:copy>
    <special/>
  </xsl:template>

  <xsl:template match="@* | node()" priority="-1">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="point[@type='1']">
    <xsl:message>stripped point</xsl:message>
  </xsl:template>

  <!-- in the first position or directly after a node() that is not part of
the group -->
  <xsl:template match="point[@type='1' and ((position() = 1) or
(preceding-sibling::*[1][self::point[not(@type='1')] or
not(self::point)]))]"
    priority="10">
    <xsl:variable name="first-point-not-in-group"
select="following-sibling::*[self::point[not(@type='1')] or
not(self::point)][1]"/>
    <group>
      <!-- apply-templates to self and everything that follows until the
next point that is not part of the group -->
      <xsl:apply-templates select=". |
following-sibling::*[generate-id(self::*) ne
generate-id($first-point-not-in-group) and
      (every $i in preceding-sibling::point satisfies (generate-id($i) ne
generate-id($first-point-not-in-group)))]" mode="grouping"/>
    </group>
  </xsl:template>

  <xsl:template match="node()" mode="grouping">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

I would be interested in ways to tackle this. Are there patterns for this
type of grouping problem? Is it favorable to put "for-each-group" stmts all
over the place? In general: are there better ways to solve this?

The sample input:
<base>
  <parentA>
    <element />
    <point value="A-A" type="1" />
    <point value="A-B" type="1" />
    <point value="A-C" type="2" />
    <point value="A-D" type="1" />
    <point value="A-E" type="2" />
    <point value="A-F" type="1" />
    <point value="A-G" type="1" />
    <point value="A-H" type="1" />
  </parentA>
  <parentB>
    <point value="B-A" type="1" />
    <point value="B-B" type="1" />
    <point value="B-C" type="2" />
    <element />
    <point value="B-D" type="1" />
    <point value="B-E" type="1" />
  </parentB>
</base>

The expected output:

<?xml version="1.0" encoding="UTF-8"?>
<base>
  <parentA>
    <element/>
    <group>
      <point value="A-A" type="1"/>
      <point value="A-B" type="1"/>
    </group>
    <point value="A-C" type="2"/>
    <group>
      <point value="A-D" type="1"/>
    </group>
    <point value="A-E" type="2"/>
    <group>
      <point value="A-F" type="1"/>
      <point value="A-G" type="1"/>
      <point value="A-H" type="1"/>
    </group>
  </parentA>
  <parentB>
    <group>
      <point value="B-A" type="1"/>
      <point value="B-B" type="1"/>
    </group>
    <point value="B-C" type="2"/>
    <element/>
    <group>
      <point value="B-D" type="1"/>
      <point value="B-E" type="1"/>
    </group>
  </parentB>
  <special/>
</base>

Thank you in advance for any insight!

Best regards
Christoph Naber
--~----------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
EasyUnsubscribe: http://lists.mulberrytech.com/unsub/xsl-list/1167547
or by email: xsl-list-unsub(_at_)lists(_dot_)mulberrytech(_dot_)com
--~--
<Prev in Thread] Current Thread [Next in Thread>