Решил это, и достаточно аккуратно и в общем, используя рекурсию, поэтому справится с произвольными уровнями.
Это может быть полезно изменено для любого, кто пытается создать структурированный документ из плоского.
--- Производительность, вероятно, ужасна, но это не большое приложение, поэтому мне все равно; 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() < $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>