xsl-list
[Top] [All Lists]

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

2014-11-19 04:27:57
Hi Heiko & Michael,

On 19.11.2014 02:20, Heiko Niemann kontakt(_at_)heiko-niemann(_dot_)de wrote:
Hi Gerrit,

since my first name is not very international I listen to a whole range of
names: from Heeeko to Hayoko. :)

Talking about ranges. I had the same question about 219, Michael clarified
and since your code covers it already I would suggest Michael to follow
your approach. They are similar anyhow.

I noticed two things though:

1) parsing the ranges does not cover missing leading zeros (if you e.g.
add 8880889 to a aircraft-range it won't show in the result, 08880889
will); first I also looked at using analyze-string but dropped it so I use
following code now (adjusted to the elements you use):

I emitted an xsl:message that would report any input token that doesn’t match '^\d{4}(\d{4})?$'. It might be a better idea to add terminate="yes" to that message instead of heuristically fixing the data, but I’m not the one to make a call on this.

2) If you add e.g. 0200 to a list the result will show:

<pass3>
     <range start="200" end="200"/>
     <range start="200" end="200"/>
     <range start="201" end="204"/>


and if you add 0201 instead the result is:

<pass3>
       <range start="201" end="200"/>
       <range start="201" end="201"/>
       <range start="202" end="204"/>

You’re right, there’s still a bug. The matching pattern in the template with xml:id="A" should be changed from
match="range[1][not(@start = following-sibling::*[1]/@start)]"
to
match="range[1][not(@start = following-sibling::*[1]/(@start|@end))]"

Let’s see which other bugs are left uncovered by our anecdotal test cases. I’m waiting for the FAA to knock on my door in order to investigate a future fatal incident caused by flaky XSLT code…

I’m posting the complete revised code below. There are no templates in #default mode any more which should help in integrating the code. I’ve added a debug parameter. Apply aircrafts.xsl with debug=yes to get the intermediate steps’ results in /ranges/debug in the output.

Gerrit

----------------------

ranges.xsl

<?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:param name="debug" select="'yes'"/>

  <xsl:template match="ranges" mode="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 (Heiko’s test case) -->
        <xsl:apply-templates select="$pass1/range" mode="ranges_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="ranges_pass3"/>
      </pass3>
    </xsl:variable>
    <ranges>
      <xsl:if test="$debug = 'yes'">
        <debug>
          <xsl:sequence select="."/>
          <xsl:sequence select="$pass1"/>
          <xsl:sequence select="$pass2"/>
        </debug>
      </xsl:if>
      <xsl:sequence select="$pass3/*"/>
    </ranges>

  </xsl:template>

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

  <xsl:template mode="ranges_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}"
      inserted-in="pass2"/>
  </xsl:template>

  <xsl:template mode="ranges_pass3" xml:id="A"
    match="range[1][not(@start = following-sibling::*[1]/(@start|@end))]">
    <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="ranges_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>

----------------------

aircrafts.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  xmlns:xs="http://www.w3.org/2001/XMLSchema";
  exclude-result-prefixes="xs"
  version="2.0">

  <xsl:import href="ranges.xsl"/>

  <xsl:param name="debug" select="'no'"/>

  <xsl:template match="group[aircraft-range]">
    <xsl:variable name="parse" as="element(ranges)">
      <ranges>
        <xsl:apply-templates mode="parse"/>
      </ranges>
    </xsl:variable>
    <xsl:apply-templates select="$parse" mode="ranges"/>
  </xsl:template>

  <xsl:template match="aircraft-range" mode="parse">
    <xsl:for-each select="tokenize(., '\s+')">
      <xsl:analyze-string select="." regex="^(\d{{4}})(\d{{4}})?$">
        <xsl:matching-substring>
<range start="{number(regex-group(1))}" end="{number((regex-group(2)[normalize-space()], regex-group(1))[1])}"/>
        </xsl:matching-substring>
        <xsl:non-matching-substring>
          <xsl:message select="'Could not parse ', ."/>
        </xsl:non-matching-substring>
      </xsl:analyze-string>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>
--~----------------------------------------------------------------
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>