XSLT Order classes in a non-alphabetical non-numer

2019-09-11 06:17发布

问题:

I have researched this problem but the suggestions that I have found seems to be rather convoluted and for a more general scenario. Perhaps there is a more concise solution for this scenario, that is more specific.

I have a large number of html files like the following:

<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
    <title>t</title>
  </head>
  <body>
    <div class="a">
      <div class="f">f1</div>
      <div class="e">e1</div>
      <div class="e">e2</div>
      <div class="g">g</div>
      <div class="c">c1</div>
      <div class="b">
        <div class="ba">ba</div>
        <div class="bb">bb</div>
      </div>
      <div class="c">c2</div>
      <div class="f">f2</div>
      <div class="d">d</div>
      <div class="c">c3</div>
    </div>
    ...
  </body>
</html>

Rule # 1 I want to order the div's inside div class="a" in a specific order of their class attribute that is non-alphabetic and non-numeric. For the purpose of this example, let's the final order be the following:

  • g
  • f
  • b
  • c
  • e
  • d

In my real examples, the list is much longer.

Rule # 2 If for a given class attribute there is more than one node, then they should be left in the same order as in the original file, for instance:

  • c1
  • c2
  • c3

Please notice that in my real examples these values would not be in alphanumerical order.

Rule # 3 The order of child nodes must not be affected, for instance:

  • ba
  • bb

Please notice that in my real examples these values would not be in alphanumerical order either.

The final output should be like the following:

<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
    <title>t</title>
  </head>
  <body>
    <div class="a">
      <div class="g">g</div>
      <div class="f">f1</div>
      <div class="f">f2</div>
      <div class="b">
        <div class="ba">ba</div>
        <div class="bb">bb</div>
      </div>
      <div class="c">c1</div>
      <div class="c">c2</div>
      <div class="c">c3</div>
      <div class="e">e1</div>
      <div class="e">e2</div>
      <div class="d">d</div>
    </div>
    ...
  </body>
</html>

I have thought at first to:

  1. Prepend a number to the class attribute value, for instance rename class="g" to class="01g", etc
  2. Order the classes in alphanumerical order
  3. Remove the number, for instance rename class="01g" to class = "g", etc

However I dislike this solution because it requires too many transformations.

What I would really like is to come up with a more elegant solutions. Perhaps I would define an ordered list of class values and a clever index would somehow put the nodes in that defined order?

Do you have any suggestions to add to my xslt template?

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

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

</xsl:stylesheet>

回答1:

AFAICT, you want to do something like:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" omit-xml-declaration="yes" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

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

<xsl:template match="div[@class='a']">
    <xsl:variable name="sort-order">gfbced</xsl:variable>
    <xsl:copy>
        <xsl:apply-templates select="@*|node()">
            <xsl:sort select="string-length(substring-before($sort-order, @class))" data-type="number" order="ascending"/>
        </xsl:apply-templates>  
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

To accommodate class values that are not single characters, you can use:

<xsl:template match="div[@class='a']">
    <xsl:variable name="sort-order">|g|f|b|c|e|d|</xsl:variable>
    <xsl:copy>
        <xsl:apply-templates select="@*|node()">
            <xsl:sort select="string-length(substring-before($sort-order, concat('|', @class, '|')))" data-type="number" order="ascending"/>
        </xsl:apply-templates>  
    </xsl:copy>
</xsl:template>


标签: xslt