XSLT с автонумерацией многоуровневого, но неструктурированного документа - PullRequest
0 голосов
/ 11 января 2012

У меня есть несколько документов 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 голосов
/ 12 января 2012

Решил это, и достаточно аккуратно и в общем, используя рекурсию, поэтому справится с произвольными уровнями.

Это может быть полезно изменено для любого, кто пытается создать структурированный документ из плоского.

--- Производительность, вероятно, ужасна, но это не большое приложение, поэтому мне все равно; o)

РЕДАКТИРОВАТЬ : На самом деле производительность была невероятной, поэтому я обновил ответ более оптимизированным; фактическая вещь, вызывающая проблему производительности, была немного странной и прокомментирована в коде, я предполагаю, что это связано с тем, как работает ленивая оценка. Без предварительной оценки полный рекурсивный прогон занимает более полутора минут, чтобы обработать мои данные (~ 400 предложений), а вместе с этим - 5 секунд. Я использую движок XSLT в .NET 4. Звучит пять секунд, но это всего лишь полный запуск приложения, включая генерацию, загрузку и отображение в браузере окончательного документа 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...