使用XSLT对列表中的数据进行排序并对表进行分组

时间:2013-07-21 20:36:02

标签: xml xslt

需要从列表中选择四种类型的数据并将其分组到表中。 XML输入的示例如下所示:

<DIV>
  <ul>
    <li>fr0.1.1 : en1.1.1</li>
    <li>fr0.2.1 : en1.2.1</li>
    <li>fr0.4.1 : en1.3.1</li>
    <li>fr0.6.1 : en1.4.1</li>
    <li>fr0.5.1 : en1.5.1</li>
    <li>fr.0.7.1 : en1.5.1</li>
    <li>        : en1.6.1</li>
  </ul
</DIV>

具有相同前缀(例如'fr')的每个3位数字中的第2个数字相对于前后3位数字的第2个数字,例如, fr0.1.1,fr0.2.1,fr0.3.1在此上下文中是连续序列,而fr0.1.1,fr0.3.1,fr0.2.1或fr0.1.1,fr0.3.1是不连续的。

我需要一个代码,只能在每个&lt; li&gt;左侧的前缀为'fr'的数字中找到这些关系中的间隙和差异。元件。如果在这样的序列中缺少一个数字,则代码需要检索它并将其列在名为“Absent(fr)”的第一列中。如果第二个数字没有按照增长顺序排列,当你在左边的所有3位数字列表中,代码应该识别第二个数字是增长顺序的差异的数字,例如,对于(0。)4(.1)&lt;(0。)6(。1)&gt;(0。)5(.1),在上面的输入示例中为fr0.6.1。如果找到这样的数字,它应该列在名为“Discrepant”的第二列中。第三列应列出差异数字在其右侧匹配的数字。

名为'No-match(en)'的第四列应列出前缀为'en'的数字,前提是它们的右边没有前缀为'fr'的匹配项。在这个例子中,这个数字是1.6.1。

第五列colled'Repetition'应列出前缀为'en'的数字,如果它只在输入列表中出现多次。代码应检索重复数字前缀为'fr'的匹配项,并将其列在最后一列'匹配'中。

HTML输出中的表应该看起来像这样(颜色或粗体等样式并不重要!):

Table

xml输出看起来像这样:

<table>
 <tr>   
   <th>Absent(en)</th>
   <th>Discrepant</th>
   <th>Match</th>
   <th>No-match(en)</th>
   <th>Repetition</th>
  <th>Matches</th>
 </tr>
 <tr>
   <td>0.3.1</td>
   <td>0.6.1</td>
   <td>1.4.1</td>
   <td>1.6.1</td>
   <td>1.5.1</td>
   <td>0.5.1, 0.7.1</td>
 </tr>
</table>

1 个答案:

答案 0 :(得分:0)

我不会回答你的整个问题。对StackOverflow来说有点太多了。我要做的是回答你问题的核心部分(第一栏)。这应该足以推断得到一个完整的答案。如果您从我的答案中推断出问题,那么将问题的未解决部分细分为较小的任务,并在每个问题上单独发布SO问题,当然,在您自己尝试完成之后。

此输入文档... (与您的略有不同,以纠正编队错误而不处理最后一个li的情况)

<DIV>
  <ul>
    <li>fr0.1.1 : en1.1.1</li>
    <li>fr0.2.1 : en1.2.1</li>
    <li>fr0.4.1 : en1.3.1</li>
    <li>fr0.6.1 : en1.4.1</li>
    <li>fr0.5.1 : en1.5.1</li>
    <li>fr0.7.1 : en1.5.1</li>
  </ul>
</DIV>

...应用于此XSLT 2.0样式表...

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:so="http://stackoverflow.com/questions/17776650"
  exclude-result-prefixes="xsl xs fn so">
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

<xsl:function name="so:middle-number" as="xs:integer">
 <xsl:param name="dotted-text" as="xs:string" />
 <xsl:sequence select="fn:replace( $dotted-text, 'fr0\.(\d+)\.1.*', '$1') cast as xs:integer" />
</xsl:function>

<xsl:function name="so:delta" as="xs:integer">
 <!-- Difference between this node and the previous.
      Count the first node as having difference = 1 . -->
 <xsl:param name="li" as="element()" />
 <xsl:sequence select="
   if ($li/preceding-sibling::li) then
       so:middle-number($li/text()) - so:middle-number($li/preceding-sibling::li[1]/text())
     else
       1" />
</xsl:function>

<xsl:function name="so:in-between" as="xs:integer*">
 <xsl:param name="lower-bound" as="xs:integer" />
 <xsl:param name="upper-bound" as="xs:integer" />
 <xsl:variable name="diff" select="$upper-bound - $lower-bound" />
 <xsl:choose>
  <xsl:when test="$diff eq 0"> 
    <xsl:sequence select="$lower-bound" />
  </xsl:when>
  <xsl:when test="$diff eq 1"> 
    <xsl:sequence select="$lower-bound, $lower-bound+1" />
  </xsl:when>
  <xsl:when test="($diff ge 2)"> 
    <xsl:variable name="half-way" select="fn:round( $lower-bound + ($diff div 2))" />
    <xsl:sequence select="so:in-between($lower-bound, $half-way) , so:in-between( $half-way+1, $upper-bound)" />
  </xsl:when>
  <xsl:otherwise />   
 </xsl:choose>
</xsl:function>

<xsl:template match="/*/ul">
 <xsl:variable name="groups">
  <xsl:for-each-group select="li" group-adjacent="so:delta(.)">
   <so:group>
    <so:start><xsl:value-of select="so:middle-number( current-group()[1     ]/text())" /></so:start>
    <so:end>  <xsl:value-of select="so:middle-number( current-group()[last()]/text())" /> </so:end>
   </so:group>
  </xsl:for-each-group>
 </xsl:variable>
 <xsl:apply-templates select="$groups" /> 
</xsl:template>

<xsl:template match="so:group[1]" />

<xsl:template match="so:group">
  <xsl:variable name="this" select="so:start/text() cast as xs:integer" as="xs:integer" />
  <xsl:variable name="prev" select="preceding-sibling::so:group[1]/so:end/text() cast as xs:integer" as="xs:integer" />
  <xsl:for-each select="for $x in so:in-between( $prev + 1, $this - 1) return $x">
   <td><xsl:value-of select="concat('0.',.,'.1')" /></td>
  </xsl:for-each> 
</xsl:template>

</xsl:stylesheet>

...产生结果......

<td>0.3.1</td>
<td>0.6.1</td>

..这是两个缺席的li值。

带回家点

您可以使用xsl:for-each-group / @ group-adjacent指令,对一个li节点的值与其前一个节点之间的差值进行分组。这为您提供了一组顺序节点。因此,只需跨越一组结束与下一组开始之间的差异,即可得到所有缺席值。这将为您提供第一列。

对其他列使用类似的技术。


更新

我刚刚意识到你不需要so:inbetween()函数。您只需使用to运算符即可。对于那个很抱歉。更改为to运算符应大大简化样式表。


更新2

这是to版本...... 我认为这是令人满意的小!

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:so="http://stackoverflow.com/questions/17776650"
  exclude-result-prefixes="xsl xs fn so">
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

<xsl:function name="so:middle-number" as="xs:integer">
 <xsl:param name="dotted-text" as="xs:string" />
 <xsl:sequence select="fn:replace( $dotted-text, 'fr0\.(\d+)\.1.*', '$1') cast as xs:integer" />
</xsl:function>

<xsl:function name="so:delta" as="xs:integer">
 <!-- Difference between this node and the previous.
      Count the first node as having difference = 1 . -->
 <xsl:param name="li" as="element()" />
 <xsl:sequence select="
   if ($li/preceding-sibling::li) then
       so:middle-number($li/text()) - so:middle-number($li/preceding-sibling::li[1]/text())
     else
       1" />
</xsl:function>

<xsl:template match="/*/ul">
 <xsl:variable name="groups">
  <xsl:for-each-group select="li" group-adjacent="so:delta(.)">
   <so:group>
    <so:start><xsl:value-of select="so:middle-number( current-group()[1     ]/text())" /></so:start>
    <so:end>  <xsl:value-of select="so:middle-number( current-group()[last()]/text())" /> </so:end>
   </so:group>
  </xsl:for-each-group>
 </xsl:variable>
 <xsl:apply-templates select="$groups" /> 
</xsl:template>

<xsl:template match="so:group[1]" />

<xsl:template match="so:group">
  <xsl:variable name="this" select="so:start/text() cast as xs:integer" as="xs:integer" />
  <xsl:variable name="prev" select="preceding-sibling::so:group[1]/so:end/text() cast as xs:integer" as="xs:integer" />
  <xsl:for-each select="for $x in $prev + 1 to $this - 1 return $x">
   <td><xsl:value-of select="concat('0.',.,'.1')" /></td>
  </xsl:for-each> 
</xsl:template>

</xsl:stylesheet>

关于XSLT太难了

关于你对XSLT的评论太多而无法理解:请记住,XSLT就像任何其他语言一样。你不能期望通过在StackOverflow上发布一两个问题来获取语言。我怀疑从长远来看,投资可以最好地满足您的利益。拿起一本关于XSLT 2的好书并阅读它。如果你选择合适的书,它实际上是一种非常容易学习的语言。这就是我所做的,而且我发现投资回报充足。