XSLT: filter and apply conditional logic while gro

2019-08-28 02:00发布

问题:

For input XML like:

<FlightOptions>
  <item>
     <Fares>
       <item>
         <FareClass>A</FareClass>
         <Fare>100</Fare>
         <FareType>E</FareType>
         <Seats>5</Seats>
       </item>
       <item>
         <FareClass>B</FareClass>
         <Fare>200</Fare>
         <FareType>E</FareType>
         <Seats>10</Seats>
       </item>
       <item>
         <FareClass>C</FareClass>
         <Fare>250</Fare>
         <FareType>E</FareType>
         <Seats>20</Seats>
       </item>
       <item>
         <FareClass>N</FareClass>
         <Fare>100</Fare>
         <FareType>F</FareType>
         <Seats>5</Seats>
       </item>
       <item>
         <FareClass>M</FareClass>
         <Fare>200</Fare>
         <FareType>F</FareType>
         <Seats>50</Seats>
       </item>
       <item>
         <FareClass>O</FareClass>
         <Fare>300</Fare>
         <FareType>F</FareType>
         <Seats>20</Seats>
       </item>
     </Fares>
     <Flight>
         <FlightNumber>YY232</FlightNumber>
         <Origin>JFK</Origin>
         <Destination>LHR</Destination>
         <DepTime>1300</DepTime>
         <ArrTime>2000</ArrTime>
     </Flight>
    </item>
</FlightOptions>

For the above XML, I need to preserve only a few Fares/item nodes where I first group by FareType (E and F) and keep the items starting from the cheapest Fare, but stopping if the Seats is >= 9. Eg., since A has only 5 seats, I need to pick the next highest fare B, but not C. Also, if Seats >= 9, I need to cap it to 9.

I am able to do the group and sort, but unable to walk the fares and apply the logic to pick the fares as long as Seats <= 9 for that FareType. The other complication is that the output XML must reorder the Fares/items such that FareType E nodes come first in descending order of Fare, followed by N node (if present in the source), and then the other FareType F nodes in descending order of Fare.

Output XML would be:

<FlightOptions>
  <item>
     <Fares>
       <item>
         <FareClass>B</FareClass>
         <Fare>200</Fare>
         <FareType>E</FareType>
         <Seats>9</Seats>
       </item>
       <item>
         <FareClass>A</FareClass>
         <Fare>100</Fare>
         <FareType>E</FareType>
         <Seats>5</Seats>
       </item>
       <item>
         <FareClass>N</FareClass>
         <Fare>100</Fare>
         <FareType>F</FareType>
         <Seats>5</Seats>
       </item>
       <item>
         <FareClass>M</FareClass>
         <Fare>200</Fare>
         <FareType>F</FareType>
         <Seats>9</Seats>
       </item>
     </Fares>
     <Flight>
         <FlightNumber>YY232</FlightNumber>
         <Origin>JFK</Origin>
         <Destination>LHR</Destination>
         <DepTime>1300</Deptime>
         <ArrTime>2000</ArrTime>
     </Flight>
    </item>
<FlightOptions>

Have been trying to readup on Muenchian grouping examples but I am having trouble understanding how to apply along with an identity transform (since I have to keep the Flight structure along with the Fares/item nodes).

Thanks!

回答1:

You could get away with not using grouping in this instance, as really all you are doing are sorting, and just excluding some elements.

So, when matching the Fares element, you sort the item elements, like so

     <xsl:apply-templates select="item">
        <xsl:sort select="FareType"/>
        <xsl:sort select="Fare"/>
     </xsl:apply-templates>

Next, when you match each such item element, you can just test for whether any previous element with the same FareType is already greater than 9. If there are no such nodes, then because you have sorted by Fare, you know to include the node.

<xsl:template match="Fares/item">
    <xsl:if test="not(preceding-sibling::item[FareType=current()/FareType][Seats > 9])">
       <!-- copy node -->
    </xsl:if>

Here is the full XSLT...

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

   <xsl:template match="Fares">
      <xsl:copy>
         <xsl:apply-templates select="item">
            <xsl:sort select="FareType"/>
            <xsl:sort select="Fare"/>
         </xsl:apply-templates>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="Fares/item">
      <xsl:if test="not(preceding-sibling::item[FareType=current()/FareType][Seats > 9])">
         <xsl:call-template name="identity"/>
      </xsl:if>
   </xsl:template>

   <xsl:template match="Seats[. > 9]">
      <xsl:copy>9</xsl:copy>
   </xsl:template>

   <xsl:template match="@*|node()">
      <xsl:call-template name="identity"/>
   </xsl:template>

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

</xsl:stylesheet>

When applied to your sample XML, the following is output:

<FlightOptions>
   <item>
      <Fares>
         <item>
            <FareClass>A</FareClass>
            <Fare>100</Fare>
            <FareType>E</FareType>
            <Seats>5</Seats>
         </item>
         <item>
            <FareClass>B</FareClass>
            <Fare>200</Fare>
            <FareType>E</FareType>
            <Seats>9</Seats>
         </item>
         <item>
            <FareClass>N</FareClass>
            <Fare>100</Fare>
            <FareType>F</FareType>
            <Seats>5</Seats>
         </item>
         <item>
            <FareClass>M</FareClass>
            <Fare>200</Fare>
            <FareType>F</FareType>
            <Seats>9</Seats>
         </item>
      </Fares>
      <Flight>
         <FlightNumber>YY232</FlightNumber>
         <Origin>JFK</Origin>
         <Destination>LHR</Destination>
         <DepTime>1300</DepTime>
         <ArrTime>2000</ArrTime>
      </Flight>
   </item>
</FlightOptions>

Do also note the template match for Seats to limit then to 9.



标签: xslt