Replace XML values with sequential alphabets in XS

2019-08-25 11:42发布

问题:

Given the input XML

<FlightOptions> 
  <item> 
     <Fares> 
       <item> 
         <FareClass>T</FareClass> 
         <Fare>100</Fare> 
         <FareType>E</FareType> 
         <Seats>5</Seats> 
       </item> 
       <item> 
         <FareClass>Y</FareClass> 
         <Fare>200</Fare> 
         <FareType>E</FareType> 
         <Seats>10</Seats> 
       </item> 
       <item> 
         <FareClass>R</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>L</FareClass> 
         <Fare>300</Fare> 
         <FareType>F</FareType> 
         <Seats>20</Seats> 
       </item> 
       <item> 
        <FareClass>K</FareClass> 
        <Fare>400</Fare> 
        <FareType>F</FareType> 
        <Seats>5</Seats> 
       </item> 
       <item> 
        <FareClass>E</FareClass> 
        <Fare>500</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> 

and applying the XSL template (Thanks Tim C for providing this!) -

 <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> 

We get the following output XML -

<FlightOptions> 
  <item> 
     <Fares> 
       <item> 
         <FareClass>T</FareClass> 
         <Fare>100</Fare> 
         <FareType>E</FareType> 
         <Seats>5</Seats> 
       </item> 
       <item> 
         <FareClass>Y</FareClass> 
         <Fare>200</Fare> 
         <FareType>E</FareType> 
         <Seats>9</Seats> 
       </item> 
       <item> 
         <FareClass>R</FareClass> 
         <Fare>250</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> 
       <item> 
         <FareClass>L</FareClass> 
         <Fare>300</Fare> 
         <FareType>F</FareType> 
         <Seats>9</Seats> 
       </item> 
       <item> 
        <FareClass>K</FareClass> 
        <Fare>400</Fare> 
        <FareType>F</FareType> 
        <Seats>5</Seats> 
       </item> 
       <item> 
        <FareClass>E</FareClass> 
        <Fare>500</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> 

What i'd like to do with the above output is to replace the FareClass tags with letters of the alphabet in sequence (A,B,C,D...) in the order each item gets selected. In addition, i'd like to leave the G and Y FareClass unchanged. [In case you have questions on the XML/XSL, I'd asked question #8074924 which was solved by Tim C.]

The output XML needs to look like:

<FlightOptions> 
  <item> 
     <Fares> 
       <item> 
         <FareClass>A</FareClass> 
         <Fare>100</Fare> 
         <FareType>E</FareType> 
         <Seats>5</Seats> 
       </item> 
       <item> 
         <FareClass>Y</FareClass> 
         <Fare>200</Fare> 
         <FareType>E</FareType> 
         <Seats>10</Seats> 
       </item> 
       <item> 
         <FareClass>B</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>C</FareClass> 
         <Fare>200</Fare> 
         <FareType>F</FareType> 
         <Seats>50</Seats> 
       </item> 
       <item> 
         <FareClass>D</FareClass> 
         <Fare>300</Fare> 
         <FareType>F</FareType> 
         <Seats>20</Seats> 
       </item> 
       <item> 
        <FareClass>E</FareClass> 
        <Fare>400</Fare> 
        <FareType>F</FareType> 
        <Seats>5</Seats> 
       </item> 
       <item> 
        <FareClass>F</FareClass> 
        <Fare>500</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> 

回答1:

If I have understood correctly, you want a solution based off of Tim's solution, but in the output have all the FareClass values re-sequenced from A to Z in output document order, but with G and Y unchanged.

I would use a pipe-line method like so. Please note that phase-1 is copied directly from Tim, only changed by adding the mode attributes.

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exslt="http://exslt.org/common"
  exclude-result-prefixes="xsl exslt"> 
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />

<xsl:variable name="phase-1-output">
  <xsl:apply-templates select="/" mode="phase-1" />
</xsl:variable>

<xsl:variable name="phase-2-output">
  <xsl:apply-templates select="exslt:node-set($phase-1-output)" mode="phase-2" />
</xsl:variable>


<xsl:template match="/">
  <xsl:copy-of select="$phase-2-output" />
</xsl:template>

<!-- ================ Phase One from Tim C ==============================-->   
   <xsl:template match="Fares" mode="phase-1"> 
      <xsl:copy> 
         <xsl:apply-templates select="item" mode="phase-1" > 
            <xsl:sort select="FareType"/> 
            <xsl:sort select="Fare"/> 
         </xsl:apply-templates> 
      </xsl:copy> 
   </xsl:template> 

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

   <xsl:template match="Seats[. > 9]" mode="phase-1"> 
      <xsl:copy>9</xsl:copy> 
   </xsl:template> 

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


<!-- ================ Phase Two ============================================-->   

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

   <xsl:template match="FareClass[.!='G'][.!='Y']" mode="phase-2"> 
      <xsl:copy> 
        <xsl:variable name="raw-count">
         <xsl:number count="item[(FareClass!='G') and (FareClass!='Y')]" /> 
        </xsl:variable>
        <xsl:number value="$raw-count + not($raw-count &lt;  7)
                                      + not($raw-count &lt; 25)" format="A" />
      </xsl:copy> 
   </xsl:template> 


</xsl:stylesheet>

...using the sample input from the original edit, yields 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>10</Seats>
      </item>
      <item>
        <FareClass>C</FareClass>
        <Fare>250</Fare>
        <FareType>E</FareType>
        <Seats>20</Seats>
      </item>
      <item>
        <FareClass>D</FareClass>
        <Fare>100</Fare>
        <FareType>F</FareType>
        <Seats>5</Seats>
      </item>
      <item>
        <FareClass>E</FareClass>
        <Fare>200</Fare>
        <FareType>F</FareType>
        <Seats>50</Seats>
      </item>
      <item>
        <FareClass>F</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>

UPDATE

Thanks Surge for the new and improved use case. The output for the updated test case, using my solution is ...

<FlightOptions>
  <item>
    <Fares>
      <item>
        <FareClass>A</FareClass>
        <Fare>100</Fare>
        <FareType>E</FareType>
        <Seats>5</Seats>
      </item>
      <item>
        <FareClass>Y</FareClass>
        <Fare>200</Fare>
        <FareType>E</FareType>
        <Seats>9</Seats>
      </item>
      <item>
        <FareClass>B</FareClass>
        <Fare>250</Fare>
        <FareType>E</FareType>
        <Seats>9</Seats>
      </item>
      <item>
        <FareClass>C</FareClass>
        <Fare>100</Fare>
        <FareType>F</FareType>
        <Seats>5</Seats>
      </item>
      <item>
        <FareClass>D</FareClass>
        <Fare>200</Fare>
        <FareType>F</FareType>
        <Seats>9</Seats>
      </item>
      <item>
        <FareClass>E</FareClass>
        <Fare>300</Fare>
        <FareType>F</FareType>
        <Seats>9</Seats>
      </item>
      <item>
        <FareClass>F</FareClass>
        <Fare>400</Fare>
        <FareType>F</FareType>
        <Seats>5</Seats>
      </item>
      <item>
        <FareClass>H</FareClass>
        <Fare>500</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>

This is slightly different from your stated expected output, but I believe the difference can be explained by the fact that you made an error (See FareClass N). By inspection, I deem this is a PASS result.

Update 2

Oops. Just noticed twos errors and have corrected them. The original erroneous template was...

<xsl:variable name="phase-2-output">
  <xsl:apply-templates select="/" mode="phase-2" />
</xsl:variable>

and

 <xsl:number count="item" />

The correct code should be...

<xsl:variable name="phase-2-output">
  <xsl:apply-templates select="exslt:node-set($phase-1-output)" mode="phase-2" />
</xsl:variable>

and

 <xsl:number count="item[(FareClass!='G') and (FareClass!='Y')]" />


回答2:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml"/>
    <xsl:template match="/">
        <xsl:apply-templates select="//Fares"/>
        </xsl:template>
    <xsl:template match="//Fares">
        <xsl:for-each select="item">
            <xsl:choose>
                <xsl:when test="not(contains(FareClass,'G') or contains(FareClass,'Y'))">
                    <item>
                        <xsl:variable name="count">
                            <xsl:number count="item"/>
                        </xsl:variable>
                        <FareClass>
                            <xsl:choose>
                            <xsl:when test="position()>1">
                                <xsl:number format="A" value="position()-1"/>
                                </xsl:when>
                                <xsl:otherwise>
                                <xsl:number format="A"/>
                                </xsl:otherwise>
                                </xsl:choose>

                        </FareClass>
                    <xsl:copy-of select="child::*[position() > 1]"/>
                    </item>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>