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: "Imsieke, Gerrit, le-tex gerrit.imsieke@xxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Tue, 18 Nov 2014 22:42:35 -0000
Hi Heiko (or Hi Ko, as we say here b sorry, I should avoid lame jokes about names b OTOH, this rule is not written in the posting guidelines, Tommie!),

That being said, I must admit that I somehow managed to ignore your preceding post when I posted my solution this afternoon. My solution suffered from the same shortcoming as yours. I now inserted a new second pass that will insert more ranges if need be.

It feels clumsier and clumsier, but it covers all test cases known so farb&

Input:

<ranges>
  <range start="100" end="300" />
  <range start="150" end="190" />
  <range start="200" end="280" />
</ranges>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  version="2.0">
  <xsl:output indent="yes"/>

<xsl:template match="/ranges">
<xsl:variable name="pass1" as="element(pass1)">
<pass1>
<!-- eliminates duplicate start/end attributes
and creates a new range for each distinct start
and end value: -->
<xsl:for-each-group select="range/(@start|@end)"
group-by="string-join((name(), .), '=')">
<xsl:sort select="." data-type="number"/>
<range>
<xsl:sequence select="."/>
<!-- adds a start attribute to the end attribute
iff the start attribute's value is smaller than
the end attribute's: -->
<xsl:sequence select="../@start[. &lt; current()]"/>
</range>
</xsl:for-each-group>
</pass1>
</xsl:variable>
<xsl:variable name="pass2" as="element(pass2)">
<pass2>
<!-- Fill gaps with new ranges (Heikobs test case) -->
<xsl:apply-templates select="$pass1/range" mode="pass2"/>
</pass2>
</xsl:variable>
<xsl:variable name="pass3" as="element(pass3)">
<pass3>
<!-- Replaces the existing start attribute with the nearest
value available, looking behind (template with xml:id="B" below).
Ranges without an end attribute will
be eliminated by virtue of the built-in template.
Except for the first range (template with xml:id="A" below):
If it doesn't have an end attribute, and if its start attribute
is not equal to the next range's start attribute, the next
suitable end attribute will be added. -->
<xsl:apply-templates select="$pass2/range" mode="pass3"/>
</pass3>
</xsl:variable>
<result>
<xsl:sequence select="$pass1"/>
<xsl:sequence select="$pass2"/>
<xsl:sequence select="$pass3"/>
</result>
</xsl:template>


  <xsl:template match="range" mode="pass2">
    <xsl:copy-of select="."/>
  </xsl:template>

<xsl:template mode="pass2"
match="range[@end]
[following-sibling::range[1]/@start &gt; current()/@end + 1]
[following-sibling::range/@start &lt; current()/@end]">
<xsl:copy-of select="."/>
<range start="{@end + 1}" end="{following-sibling::range[1]/@start - 1}"/>
</xsl:template>


  <xsl:template mode="pass3" xml:id="A"
    match="range[1][not(@start = following-sibling::*[1]/@start)]">
    <xsl:copy>
      <xsl:copy-of select="@start"/>
      <xsl:attribute name="end"
        select="following-sibling::range[@start][1]/@start - 1"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="range[@end]" mode="pass3" xml:id="B">
    <xsl:copy>
      <xsl:attribute name="start"
        select="max((@start,
                     preceding-sibling::*[@start][1]/@start,
                     preceding-sibling::*[@end][1]/@end + 1))"/>
      <xsl:copy-of select="@end"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>


Output:


<?xml version="1.0" encoding="UTF-8"?>
<result>
   <pass1>
      <range start="100"/>
      <range start="150"/>
      <range end="190" start="150"/>
      <range start="200"/>
      <range end="280" start="200"/>
      <range end="300" start="100"/>
   </pass1>
   <pass2>
      <range start="100"/>
      <range start="150"/>
      <range end="190" start="150"/>
      <range start="191" end="199"/>
      <range start="200"/>
      <range end="280" start="200"/>
      <range end="300" start="100"/>
   </pass2>
   <pass3>
      <range start="100" end="149"/>
      <range start="150" end="190"/>
      <range start="191" end="199"/>
      <range start="200" end="280"/>
      <range start="281" end="300"/>
   </pass3>
</result>

Gerrit


On 18.11.2014 19:03, Heiko Niemann kontakt@xxxxxxxxxxxxxxxx wrote:
Hi again,

I just noticed two shortcomings in the solution I posted:

1) the attribute 'data-type' in the xsl:sort element needs to be set to
'number'


2) some cases are not covered


e.g. if the input is

<ranges>
   <range>100-300</range>
   <range>150-190</range>
   <range>200-280</range>
</ranges>

the output holds:

<ranges>
   <range>100-149</range>
   <range>150-190</range>
   <range>200-280</range>
   <range>281-300</range>
</ranges>


so there is a gap (191-199). The output rather should be:


<ranges>
   <range>100-149</range>
   <range>150-190</range>
   <range>191-199</range> <!-- gap -->
   <range>200-280</range>
   <range>281-300</range>
</ranges>


Michael could you verify that this would be the expected output.


So I guess it's more tricky.

Michael, could you please provide samples of possible inputs and expected
results.

Cheers,
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




-- Gerrit Imsieke GeschC$ftsfC<hrer / Managing Director le-tex publishing services GmbH Weissenfelser Str. 84, 04229 Leipzig, Germany Phone +49 341 355356 110, Fax +49 341 355356 510 gerrit.imsieke@xxxxxxxxx, http://www.le-tex.de

Registergericht / Commercial Register: Amtsgericht Leipzig
Registernummer / Registration Number: HRB 24930

GeschC$ftsfC<hrer: Gerrit Imsieke, Svea Jelonek,
Thomas Schmidt, Dr. Reinhard VC6ckler

Current Thread