Re: [xsl] Creating new, distinct groups of ranges from an aggregation of individual ranges

Subject: Re: [xsl] Creating new, distinct groups of ranges from an aggregation of individual ranges
From: "Heiko Niemann kontakt@xxxxxxxxxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Tue, 18 Nov 2014 13:02:33 -0000
Hi Michael,

this would be my solution:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0">
  <xsl:output method="xml" encoding="UTF-8"/>
  <xsl:variable as="element()+" name="bounds">
    <xsl:for-each select="/ranges/range">
      <xsl:for-each select="tokenize(string(.), ',')">
        <bor val="{normalize-space(tokenize(.,'-')[1])}"/>
        <eor val="{normalize-space(tokenize(.,'-')[2])}"/>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:variable>
  <xsl:variable as="element()" name="grouped">
    <grouped>
      <xsl:for-each-group select="$bounds" group-by="@val">
        <xsl:sort select="@val"/>
        <xsl:sequence select="current-group()[1]"/>
      </xsl:for-each-group>
    </grouped>
  </xsl:variable>
  <xsl:variable as="element()" name="completed">
    <completed>
      <xsl:for-each select="$grouped/*">
        <xsl:copy>
          <xsl:copy-of select="@*"/>
          <xsl:choose>
            <xsl:when test="local-name() = 'bor'">
              <xsl:attribute select="following-sibling::bor[1]/@val - 1"
name="end"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:attribute select="preceding-sibling::eor[1]/@val + 1"
name="beg"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:copy>
      </xsl:for-each>
    </completed>
  </xsl:variable>
  <xsl:template match="/">
    <result>
      <bounds>
        <xsl:copy-of select="$bounds"/>
      </bounds>
      <grouped>
        <xsl:copy-of select="$grouped/*"/>
      </grouped>
      <completed>
        <xsl:copy-of select="$completed/*"/>
      </completed>
      <ranges>
        <xsl:for-each-group group-starting-with="bor |
eor[preceding-sibling::*[1]/local-name() eq 'eor']"
select="$completed/*">
          <xsl:variable name="bor" select="current-group()[local-name() eq
'bor'][1]"/>
          <xsl:variable name="eor" select="current-group()[local-name() eq
'eor'][1]"/>
          <range>
            <xsl:value-of separator="-">
              <xsl:sequence select="($bor/@val, $eor/@beg, 'err')[1]"/>
              <xsl:sequence select="($eor/@val, $bor/@end, 'err')[1]"/>
            </xsl:value-of>
          </range>
        </xsl:for-each-group>
      </ranges>
    </result>
  </xsl:template>
</xsl:stylesheet>

With this input:

<ranges>
  <range>150-202</range>
  <range>201-225</range>
  <range>201-204</range>
  <range>205-234</range>
  <range>226-234, 250-260</range>
</ranges>

You'll get this output (which includes the results of the different steps):

<result>
  <bounds>
    <bor val="150"/>
    <eor val="202"/>
    <bor val="201"/>
    <eor val="225"/>
    <bor val="201"/>
    <eor val="204"/>
    <bor val="205"/>
    <eor val="234"/>
    <bor val="226"/>
    <eor val="234"/>
    <bor val="250"/>
    <eor val="260"/>
  </bounds>
  <grouped>
    <bor val="150"/>
    <bor val="201"/>
    <eor val="202"/>
    <eor val="204"/>
    <bor val="205"/>
    <eor val="225"/>
    <bor val="226"/>
    <eor val="234"/>
    <bor val="250"/>
    <eor val="260"/>
  </grouped>
  <completed>
    <bor val="150" end="200"/>
    <bor val="201" end="204"/>
    <eor val="202" beg=""/>
    <eor val="204" beg="203"/>
    <bor val="205" end="225"/>
    <eor val="225" beg="205"/>
    <bor val="226" end="249"/>
    <eor val="234" beg="226"/>
    <bor val="250" end=""/>
    <eor val="260" beg="235"/>
  </completed>
  <ranges>
    <range>150-200</range>
    <range>201-202</range>
    <range>203-204</range>
    <range>205-225</range>
    <range>226-234</range>
    <range>250-260</range>
  </ranges>
</result>


So I take four steps:

1) extract beginning/end of range; create bor/eor elements for each;
unsorted/duplicates

2) remove duplicates and sort bounds (which leaves out that there might be
a bor and a eor that have the same value, but you did not mention what to
do in this case, can be added I guess)

So now I have a sequence of bor and eor elements which do not alternate
yet, this means I will have to include a eor element when a bor follows a
bor and vice versa. This is solved in step 4.

3) adding attributes for 'possible' beginning and end: each bor gets an
end-Attribute with the value of the next following bor minus 1; each eor
gets a beg-Attribute with the next preceding eor plus 1; the first eor and
the last bor will be blank for those attributes which is ok since I assume
they won't be needed because there should be a bor at the beginning of the
sequence and an eor at the end. Anyhow I don't know yet whether these
attributes will be needed but it is possible.


4) grouping: group starting either with a bor or an eor preceded by an
eor. So a group will either consist of a single bor, a single eor or of
bor and eor. So the start of a range will either be the value (@val) of
bor or - if bor is absent - the 'possible' beg of eor. Similar logic for
the end of a range.


Hopefully it's not to confusing. The code probably can be optimized (I
would like to know whether I could get rid of local-name() e.g.).


Heiko







> Greetings,
> I'm trying to use XSLT 2.0 to create a new set of grouped ranges based on
> the overlap of an aggregation of a set of non-contiguous individual
> ranges. Example:
> Given a range of numbers as an individual set:1. <range>150-202</range>2.
> <range>201-225</range>3. <range>201-204</range>4. <range>205-234</range>5.
> <range>226-234, 250-260</range>
> I'm trying to produce a new grouping based on the way the groups
> overlap:150-200 (this is where <range> 1 starts and overlaps to 2 &
> 3)201-202 (this is where 1 & 2 overlap, and group 1 ends)203-204 (this is
> where 2 & 3 overlap and 3 ends)205-225 (this is where 4 starts and begins
> to overlap with 5)226-234 (this is where 4 & 5 overlap and end for the
> first part of 5)250-260 (this is where the second range in 5 exists)
> The start and end point of the individual source ranges form the
> boundaries.
> I expect to end up with a string or variable structure
> like:<finalrange><range>150-200</range><range>201-202</range>etc</finalrange>or:
> <range start="150" end="200"/><range start="201" end="202"/>etc
> Ultimately I have to format some content in XSLFO based on the XML's
> participation in the "new" given range grouping. If you know aircraft
> effectivity, this is what I am trying to group.
> I've been using <xsl:sequence> to find all the numbers of a single range,
> so I can do compares against individual numbers in the entire range, if
> necessary. But, it seems like it may be easier to just work with the
> boundaries: the start and end points and see if a value falls within it,
> somehow, rather than iterating repetitively through enumerations of
> sequences.
> I've been searching the archives for a while and have found some evocative
> possibilities from Dimitre Novatchev and Michael Kay, but I can't quite
> find a way to work with the overlapping. I'm continuing to study their
> ranging/grouping examples, but help would be appreciated!
> Thanks,Michael Friedman

Current Thread