XSLT自动编号多级但非结构化的文档

时间:2012-01-11 16:08:01

标签: xml xslt

我有许多XML(实际上是XHTML)文档,其中包含我想要自动编号的多级子句;问题是文档是非结构化的平面列表,其中包含文本属性中指示的级别。是的,我知道,但我无法改变它们;太多太大了。

显示我想要的结构和标签的简化案例是:

<root>
  <p label="1" class="clause_L1">A</p>
  <p label="1.1" class="clause_L2">B</p>
  <p label="1.2" class="clause_L2">C</p>
  <p label="1.3" class="clause_L2">D</p>
  <p label="2" class="clause_L1">E</p>
  <p label="3" class="clause_L1">F</p>
  <p label="4" class="clause_L1">G</p>
  <p label="4.1" class="clause_L2">H</p>
  <p label="4.1.1" class="clause_L3">I</p>
  <p label="4.1.2" class="clause_L3">J</p>
  <p label="4.2" class="clause_L2">K</p>
  <p label="4.3" class="clause_L2">L</p>
</root>

经过多次黑客攻击,我得到了以下样式表:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" indent="yes" />

<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>

<xsl:template match="p[substring(@class, 1, 6) = 'clause']">
   <xsl:variable name="class" select="@class"/>
   <xsl:variable name="level" select="substring(@class, 9, 1)"/>
   <xsl:variable name="parent" select="preceding-sibling::p[@class = concat('clause_L', $level - 1)][1]"/>
   <xsl:variable name="parentPos" select="count($parent/preceding-sibling::p[substring(@class, 1, 6) = 'clause']) + 1"/>

   <clause>
    <xsl:attribute name="parent">
      <xsl:value-of select="$parent/@label"/>
    </xsl:attribute>
    <xsl:attribute name="parentPos">
      <xsl:value-of select="$parentPos"/>
    </xsl:attribute>
    <xsl:attribute name="origLabel">
       <xsl:value-of select="@label"/>
    </xsl:attribute>
    <xsl:attribute name="label">
       <xsl:number count="p[string($parent) = '' or position() &gt; $parentPos][@class = $class]" />
    </xsl:attribute>
    <xsl:value-of select="."/>
   </clause>
</xsl:template>

...它为我提供了最低当前级别的正确编号:

<clause parent="" parentPos="1" origLabel="1" label="1">A</clause>
<clause parent="1" parentPos="1" origLabel="1.1" label="1">B</clause>
<clause parent="1" parentPos="1" origLabel="1.2" label="2">C</clause>
<clause parent="1" parentPos="1" origLabel="1.3" label="3">D</clause>
<clause parent="" parentPos="1" origLabel="2" label="2">E</clause>
<clause parent="" parentPos="1" origLabel="3" label="3">F</clause>
<clause parent="" parentPos="1" origLabel="4" label="4">G</clause>
<clause parent="4" parentPos="7" origLabel="4.1" label="1">H</clause>
<clause parent="4.1" parentPos="8" origLabel="4.1.1" label="1">I</clause>
<clause parent="4.1" parentPos="8" origLabel="4.1.2" label="2">J</clause>
<clause parent="4" parentPos="7" origLabel="4.2" label="2">K</clause>
<clause parent="4" parentPos="7" origLabel="4.3" label="3">L</clause>

首先,是否可以在此模板中为标签生成更高级别的数字,因为看起来xsl:number仅适用于当前上下文 - 所以我不知道如何获得当前的下一级别计数器。

其次是有更好的方法来完成这个句号吗?请记住,我坚持使用源数据格式。

1 个答案:

答案 0 :(得分:0)

解决它,并以合理的整洁和一般的方式使用递归,因此将处理任意级别。

对于试图从平面文档创建结构化文档的任何人来说,这都可以进行有用的修改。

---性能可能很糟糕,但这不是一个高容量的应用程序,所以我不在乎; o)

编辑:表演实际上是不可思议的,所以我用更优化的答案更新了答案;导致性能问题的实际情况有点奇怪,并在代码中进行了评论,我认为它与懒惰评估的工作原理有关。如果没有预先评估,完整的递归运行需要花费一分半钟来运行我的数据(~400个子句),并且需要5秒钟。我在.NET 4中使用XSLT引擎。五秒钟听起来有一段时间,但这只是完整的应用程序运行,包括PDF最终文档的生成,下载和浏览器呈现,我还没有测量到转换单独

使用上面的源XML,XSL是:

<xsl:template match="p[substring(@class, 1, 6) = 'clause']">
   <clause>
    <xsl:attribute name="origLabel">
         <xsl:value-of select="@label"/>
      </xsl:attribute>
    <xsl:attribute name="label">
       <xsl:call-template name="makeLabel">
           <xsl:with-param name="node" select="."/>
        </xsl:call-template>
     </xsl:attribute>
     <xsl:value-of select="."/>
   </clause>
</xsl:template>


<xsl:template name="makeLabel">
   <xsl:param name="node"/>

   <xsl:variable name="class" select="$node/@class"/>
   <xsl:variable name="level" select="substring($class, 9, 1)"/>
   <xsl:variable name="parent" select="$node/preceding-sibling::p[@class = concat('clause_L', $level - 1)][1]"/>
   <xsl:variable name="parentPos" select="count($parent/preceding-sibling::p[substring(@class, 1, 6) = 'clause']) + 1"/>
   <xsl:variable name="myPos" select="count($node/preceding-sibling::p[substring(@class, 1, 6) = 'clause']) + 1"/>
   <xsl:variable name="precPos" select="number($myPos - $parentPos)"/>
   <xsl:variable name="topLevel" select="not($parent)"/>

   <xsl:if test="string($parent) != ''">
       <xsl:call-template name="makeLabel">
           <xsl:with-param name="node" select="$parent"/>
        </xsl:call-template>
   </xsl:if>

   <xsl:choose>
    <xsl:when test="$topLevel">
      <xsl:value-of select="count($node/preceding-sibling::p[@class = $class]) + 1" />
    </xsl:when>
    <xsl:otherwise>
      <!--
        It is ABSOLUTELY REQUIRED to pre-evaluate $precPos before the count(); without this the request takes over a minute to run; 
        with it, it takes ~5 seconds.
      -->
      <xsl:comment><xsl:value-of select="$precPos"/></xsl:comment>
      <!-- -->
      <xsl:value-of select="count($node/preceding-sibling::p[position() &lt; $precPos][@class = $class]) + 1" />
    </xsl:otherwise>
   </xsl:choose>    
   <xsl:text>.</xsl:text>
</xsl:template>

这给出了预期的结果:

<clause origLabel="1" label="1.">A</clause>
<clause origLabel="1.1" label="1.1.">B</clause>
<clause origLabel="1.2" label="1.2.">C</clause>
<clause origLabel="1.3" label="1.3.">D</clause>
<clause origLabel="2" label="2.">E</clause>
<clause origLabel="3" label="3.">F</clause>
<clause origLabel="4" label="4.">G</clause>
<clause origLabel="4.1" label="4.1.">H</clause>
<clause origLabel="4.1.1" label="4.1.1.">I</clause>
<clause origLabel="4.1.2" label="4.1.2.">J</clause>
<clause origLabel="4.2" label="4.2.">K</clause>
<clause origLabel="4.3" label="4.3.">L</clause>