Search code examples
xsltxslt-2.0

How to select range's between IDs


Please suggest for how to select the range's between IDs. Example if range is 5-8, then 6,7 are required ids. If figs <link href="fig3">-<link href="fig7">, then fig4 fig5 fig6 are required IDs.

XML:

<root>
<p id="p1">This <link href="#fig-0001 #fig-0002"/>, <link href="#fig-0003"/>-<link href="#fig-0006"/></p>
<figure xml_id="fig-0001"><label>Fig. 1</label><caption><p>One</p></caption></figure>
<figure xml_id="fig-0002"><label>Fig. 2</label><caption><p>Two</p></caption></figure>
<figure xml_id="fig-0003"><label>Fig. 3</label><caption><p>Three</p></caption></figure>
<figure xml_id="fig-0004"><label>Fig. 4</label><caption><p>Four</p></caption></figure>
<figure xml_id="fig-0005"><label>Fig. 5</label><caption><p>Five</p></caption></figure>
<figure xml_id="fig-0006"><label>Fig. 6</label><caption><p>Six</p></caption></figure>
</root>

XSLT2:

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

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

<xsl:key name="kFloat" match="*" use="@xml_id"/>
<xsl:template match="link[ancestor::p/following-sibling::*[1][matches(name(), '^(figure)$')]][matches(key('kFloat', if(contains(@href, ' ')) then substring-after(substring-before(@href, ' '), '#') else substring-after(@href, '#'))/name(), '^(figure)$')]">
    <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy><!-- link element retaining-->
    <!--Range between  IDs  selection -->
    <xsl:if test="matches(preceding-sibling::node()[1][self::text()], '^(&amp;#x2013;|&amp;#x02013;|–|-)$')">
        <xsl:variable name="varRangeFirst" select="substring-after(preceding-sibling::node()[2][name()='link']/@href, '#')"/>
        <xsl:variable name="varRangeLast" select="substring-after(@href, '#')"/>
        <xsl:variable name="varRangeBetweenIDs1">
            <!--xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure return $i/@xml_id"/--><!-- this will select all preceding figures, but it should  between 3 and 6 -->
            <xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure[for $k in preceding-sibling::figure return contains($k/@xml_id, $varRangeFirst)] return $i/@xml_id"/><!-- here getting error--><!-- please suggest to select range's between IDs from this -->
            <!--xsl:if test="matches(key('kFloat', $varRangeLast)/name(), '^(figure)$')">
                <xsl:for-each select="key('kFloat', $varRangeLast)/preceding-sibling::figure">
                    <a><xsl:value-of select="@xml_id"/></a>
                </xsl:for-each>
            </xsl:if-->
        </xsl:variable>

            <xsl:for-each select="$varRangeBetweenIDs1/a">
                <xsl:variable name="var2"><xsl:value-of select="preceding-sibling::a"/></xsl:variable>
                <xsl:if test="contains($var2, $varRangeFirst)">
                    <xsl:element name="float"><xsl:attribute name="id" select="."/></xsl:element>
                </xsl:if>
            </xsl:for-each>
    </xsl:if>
    <xsl:for-each select="tokenize(@href, ' ')"><!--for each link's individual hrefs will have respective float element -->
        <float id="{substring-after(., '#')}"/>
    </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Required Result:

<root>
<p id="p1">This <link href="#fig-0001 #fig-0002"/><float id="fig-0001"/><float id="fig-0002"/>, <link href="#fig-0003"/><float id="fig-0003"/>-<link href="#fig-0006"/><float id="fig-0004"/><float id="fig-0005"/><float id="fig-0006"/></p>
<figure xml_id="fig-0001"><label>Fig. 1</label><caption><p>One</p></caption></figure>
<figure xml_id="fig-0002"><label>Fig. 2</label><caption><p>Two</p></caption></figure>
<figure xml_id="fig-0003"><label>Fig. 3</label><caption><p>Three</p></caption></figure>
<figure xml_id="fig-0004"><label>Fig. 4</label><caption><p>Four</p></caption></figure>
<figure xml_id="fig-0005"><label>Fig. 5</label><caption><p>Five</p></caption></figure>
<figure xml_id="fig-0006"><label>Fig. 6</label><caption><p>Six</p></caption></figure>
</root>

Solution

  • I couldn't quite work out your logic from your current XSLT, so I would consider a different approach, using templates to match the various types of link element you required. Specifically, have separate ones for link elements that precede or follow a text node with a hyphen in.

    Try this XSLT. This makes use of the intersect function to get the range of elements you require in the case of 003-006.

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:key name="kFloat" match="figure" use="@xml_id"/>
    
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>
    </xsl:template>
    
    <xsl:template match="p/link[following-sibling::node()[1][self::text()][. = '-']][following-sibling::node()[2][self::link]]" priority="3">
        <xsl:call-template name="identity" />
        <xsl:apply-templates select="key('kFloat', substring-after(@href, '#'))" mode="float" />
    </xsl:template>
    
    <xsl:template match="p/link[preceding-sibling::node()[1][self::text()][. = '-']][preceding-sibling::node()[2][self::link]]" priority="2">
        <xsl:call-template name="identity" />
        <xsl:variable name="firstLink" select="preceding-sibling::node()[2]" />
        <xsl:apply-templates select="key('kFloat', substring-after($firstLink/@href, '#'))/following-sibling::figure intersect key('kFloat', substring-after(@href, '#'))/preceding-sibling::figure" mode="float" />
        <xsl:apply-templates select="key('kFloat', substring-after(@href, '#'))" mode="float" />
    </xsl:template>
    
    <xsl:template match="p/link[@href]">
        <xsl:next-match />
        <xsl:variable name="doc" select="/" />
        <xsl:for-each select="for $ref in tokenize(@href, ' ') return substring-after($ref, '#')">
            <xsl:apply-templates select="key('kFloat', ., $doc)" mode="float" />
        </xsl:for-each>
    </xsl:template>
    
    <xsl:template match="figure" mode="float">
        <float id="{@xml_id}"/>    
    </xsl:template>
    </xsl:stylesheet>
    

    See it in action at http://xsltfiddle.liberty-development.net/ncdD7nu