Search code examples
xsltxslt-2.0

How to sort data with group-adjecment in XSLT


How to sort data descending order? In parentheses some year string is same but before year string some text e.g. Mendoza v Nordstrom, Inc. (2017) and Mendoza v Nordstrom, Inc. (9th Cir 2017). We want change only before year same text then I am using substring-before(named-content[@content-type = 'emEntry'], '('). If year is different then code is working fine e.g. AFL-CIO v Unemployment Ins. Appeals Bd. (1994) and AFL-CIO v Unemployment Ins. Appeals Bd. (1996). If coming is same year then code not work. Please check last two entry's
Input XML

<root>
<p content-type="emLetHead">A</p>
<p content-type="emCase"><named-content content-type="emEntry">A.M. v Albertsons, LLC (2009) 178 CA4th 455:</named-content></p>
<p content-type="emCase"><named-content content-type="emEntry">ABBA Rubber Co. v Seaquist (1991) 235 CA3d 1:</named-content></p>
<p content-type="emCase"><named-content content-type="emEntry">AFL-CIO v Unemployment Ins. Appeals Bd. (1994) 23 CA4th 51:</named-content></p>
<p content-type="emCase"><named-content content-type="emEntry">AFL-CIO v Unemployment Ins. Appeals Bd. (1996) 13 C4th 1017:</named-content></p>
<p content-type="emCase"><named-content content-type="emEntry">Mendoza v Nordstrom, Inc. (2017) 2 C5th 1074:</named-content></p>
<p content-type="emCase"><named-content content-type="emEntry">Mendoza v Nordstrom, Inc. (9th Cir 2017) 865 F3d 1261:</named-content></p>
</root>

XSLT:

    <xsl:function name="mf:extract-year" as="xs:integer?">
    <xsl:param name="input" as="xs:string"/>
    <xsl:sequence
        select="analyze-string($input, '\(.*?([0-9]{4}).*\)')/*:match/*:group[@nr = 1]/xs:integer(.)"/>
</xsl:function>

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

<xsl:template match="root">
    <xsl:copy>
        <xsl:for-each-group select="p" group-adjacent="substring-before(named-content[@content-type = 'emEntry'], '(')">
            <xsl:apply-templates select="current-group()">
                <xsl:sort select="let $year := mf:extract-year(.)
                    return if ($year) then -$year else 1"/>
            </xsl:apply-templates>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

Expected output

<root>
   <p content-type="emLetHead">A</p>
   <p content-type="emCase">
      <named-content content-type="emEntry">A.M. v Albertsons, LLC (2009) 178 CA4th 455:</named-content>
   </p>
   <p content-type="emCase">
      <named-content content-type="emEntry">ABBA Rubber Co. v Seaquist (1991) 235 CA3d 1:</named-content>
   </p>
   <p content-type="emCase">
      <named-content content-type="emEntry">AFL-CIO v Unemployment Ins. Appeals Bd. (1996) 13 C4th 1017:</named-content>
   </p>
   <p content-type="emCase">
      <named-content content-type="emEntry">AFL-CIO v Unemployment Ins. Appeals Bd. (1994) 23 CA4th 51:</named-content>
   </p>
   <p content-type="emCase">
      <named-content content-type="emEntry">Mendoza v Nordstrom, Inc. (9th Cir 2017) 865 F3d 1261:</named-content>
   </p>
   <p content-type="emCase">
      <named-content content-type="emEntry">Mendoza v Nordstrom, Inc. (2017) 2 C5th 1074:</named-content>
   </p>
</root>

Code https://xsltfiddle.liberty-development.net/3NSTbfj/20


Solution

  • Perhaps extracting anything inside () and sorting as a string suffices:

    <xsl:function name="mf:extract-sort" as="xs:string?">
        <xsl:param name="input" as="xs:string"/>
        <xsl:sequence
            select="analyze-string($input, '\((.*?)\)')/*:match/*:group[@nr = 1]"/>
    </xsl:function>
    
    <xsl:template match="root">
        <xsl:copy>
            <xsl:for-each-group select="p" group-adjacent="substring-before(named-content[@content-type = 'emEntry'], '(')">
                <xsl:apply-templates select="current-group()">
                    <xsl:sort select="let $sort := mf:extract-sort(.)
                        return if ($sort) then $sort else 1" order="descending"/>
                </xsl:apply-templates>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    

    https://xsltfiddle.liberty-development.net/pNmC4Jf

    Or perhaps use several xsl:sort for the different parts in the desired order/priority:

    <xsl:function name="mf:extract-year" as="xs:integer?">
        <xsl:param name="input" as="xs:string"/>
        <xsl:sequence
            select="analyze-string($input, '\((.*?)([0-9]{4})\)')/*:match/*:group[@nr = 2]"/>
    </xsl:function>
    
    <xsl:function name="mf:extract-sort" as="xs:string?">
        <xsl:param name="input" as="xs:string"/>
        <xsl:sequence
            select="analyze-string($input, '\((.*?)\)')/*:match/*:group[@nr = 1]"/>
    </xsl:function>
    
    
    <xsl:template match="root">
        <xsl:copy>
            <xsl:for-each-group select="p" group-adjacent="substring-before(named-content[@content-type = 'emEntry'], '(')">
                <xsl:apply-templates select="current-group()">
                    <xsl:sort select="let $year := mf:extract-year(.)
                        return if ($year) then -$year else 1"/>
                    <xsl:sort select="let $sort := mf:extract-sort(.)
                        return if ($sort) then $sort else 1" order="descending"/>
                </xsl:apply-templates>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>
    

    https://xsltfiddle.liberty-development.net/pNmC4Jf/3