Sorting on attribute collected from reference list

2019-08-29 07:30发布

I am trying to sort a list of categorized xml elements, using XSLT 2.0. Each element has a unique ID and the categorization is defined in another list containing these and more elements. Here's an example of a starting XML document. The section that I want sorted is /Atlas/VisitedCities. It should be sorted according to area of the world and date of the visit:

<?xml version="1.0" encoding="UTF-8"?>
<Atlas>
    <Cities>
        <City id="1" worldPart="Africa">
            <Name>Luxor</Name>
            <Founded>-3200</Founded>
            <Location>Egypt</Location>
        </City>
        <City id="2" worldPart="Africa">
            <Name>Tripoli</Name>
            <Founded>-700</Founded>
            <Location>Libya</Location>
        </City>
        <City id="3" worldPart="Americas">
            <Name>Cholula</Name>
            <Founded>-200</Founded>
            <Location>Mexico</Location>
        </City>
        <City id="4" worldPart="Americas">
            <Name>Flores</Name>
            <Founded>-1000</Founded>
            <Location>Guatemala</Location>
        </City>
        <City id="5" worldPart="Europe">
            <Name>Argos</Name>
            <Founded>-5000</Founded>
            <Location>Greece</Location>
        </City>
        <City id="6" worldPart="Europe">
            <Name>Athens</Name>
            <Founded>-4000</Founded>
            <Location>Greece</Location>
        </City>
    </Cities>
    <VisitedCities lastUpdate="2018-09-10">
        <VisitedCity cityID="6">
            <Date>1883-08-26</Date>
            <Visitor>Dora</Visitor>
        </VisitedCity>
        <VisitedCity cityID="3">
            <Date>1907-01-02</Date>
            <Visitor>Nemo</Visitor>
        </VisitedCity>
        <VisitedCity cityID="4">
            <Date>1940-02-08</Date>
            <Visitor>Jimenez</Visitor>
        </VisitedCity>
        <VisitedCity cityID="2">
            <Date>1886-06-10</Date>
            <Visitor>James T. Kirk</Visitor>
        </VisitedCity>
    </VisitedCities>
</Atlas>

The wanted output is this:

<?xml version="1.0" encoding="UTF-8"?>
<Atlas>
    <Cities>
        <City id="1" worldPart="Africa">
            <Name>Luxor</Name>
            <Founded>-3200</Founded>
            <Location>Egypt</Location>
        </City>
        <City id="2" worldPart="Africa">
            <Name>Tripoli</Name>
            <Founded>-700</Founded>
            <Location>Libya</Location>
        </City>
        <City id="3" worldPart="Americas">
            <Name>Cholula</Name>
            <Founded>-200</Founded>
            <Location>Mexico</Location>
        </City>
        <City id="4" worldPart="Americas">
            <Name>Flores</Name>
            <Founded>-1000</Founded>
            <Location>Guatemala</Location>
        </City>
        <City id="5" worldPart="Europe">
            <Name>Argos</Name>
            <Founded>-5000</Founded>
            <Location>Greece</Location>
        </City>
        <City id="6" worldPart="Europe">
            <Name>Athens</Name>
            <Founded>-4000</Founded>
            <Location>Greece</Location>
        </City>
    </Cities>
    <VisitedCities lastUpdate="2018-09-10">
        <VisitedCity cityID="2">
            <Date>1886-06-10</Date>
            <Visitor>James T. Kirk</Visitor>
        </VisitedCity>
        <VisitedCity cityID="6">
            <Date>1883-08-26</Date>
            <Visitor>Dora</Visitor>
        </VisitedCity>
        <VisitedCity cityID="3">
            <Date>1907-01-02</Date>
            <Visitor>Nemo</Visitor>
        </VisitedCity>
        <VisitedCity cityID="4">
            <Date>1940-02-08</Date>
            <Visitor>Jimenez</Visitor>
        </VisitedCity>
    </VisitedCities>
</Atlas>

The stylesheet (XSLT 2.0) that I am struggling with looks like this:

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

    <!-- Format output -->
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*" />

    <!-- Copy everything that does not match later templates. -->
    <xsl:template match="node()|@*" priority="-1">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:variable name="city.list" select="/Atlas/Cities"/>

    <xsl:variable name="sort-order" as="element()*">
        <wPart>Africa</wPart>
        <wPart>Europe</wPart>
        <wPart>Americas</wPart>
    </xsl:variable>

    <xsl:template match="/Atlas/VisitedCities">
        <xsl:variable name="city-list" select="."/>
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:for-each select="$sort-order">
                <xsl:variable name="this-wpart" select="./text()"/>
                <!-- How to select VisitedCity based on info in other list??? -->
                <xsl:apply-templates select="$city-list/VisitedCity[$city.list/City[@cityID=$city-list/VisitedCity/@cityID]/@worldPart=$this-wpart]">
                    <xsl:sort select="./Date"/>
                </xsl:apply-templates>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

I think I understand why this stylesheet will not work (it does not sort at all), as I don't know how to make the selection in the (last) apply-templates. I don't see how to refer to the outermost elements from the inner parts of this expression.

标签: xslt xpath
1条回答
虎瘦雄心在
2楼-- · 2019-08-29 07:56

It might suffice to set up a key to reference the City elements by the id attribute to then, in the xsl:sort select expression reference the worldPart attribute. Additionally you could replace the for-each on your continent order with an index-of() call with

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>

  <xsl:key name="city-by-id" match="Cities/City" use="@id"/>

    <xsl:variable name="sort-order" as="element()*">
        <wPart>Africa</wPart>
        <wPart>Europe</wPart>
        <wPart>Americas</wPart>
    </xsl:variable>

  <xsl:template match="VisitedCities">
      <xsl:copy>
          <xsl:apply-templates select="VisitedCity">
              <xsl:sort select="index-of($sort-order, key('city-by-id', @cityID)/@worldPart)"/>
              <xsl:sort select="Date"/>
          </xsl:apply-templates>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

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

That complete example is XSLT 3 but to use it with XSLT 2 you would just replace the xsl:mode declaration in there with your template you have prefixed with the comment <!-- Copy everything that does not match later templates. -->, that is, with the identity transformation template.

查看更多
登录 后发表回答