【发布时间】:2016-10-27 00:06:38
【问题描述】:
鉴于此源 XML 文档:input.xml
<body>
<p ilvl="1">content</p>
<p ilvl="1">content</p>
<p ilvl="2">content</p>
<p ilvl="3">content</p>
<p ilvl="1">content</p>
<p ilvl="2">content</p>
<p ilvl="2">content</p>
<p ilvl="3">content</p>
<p ilvl="1">content</p>
<p ilvl="1">content</p>
<p ilvl="3">content</p>
</body>
我想转换成 output.xml:
<list>
<item>
<list>
<item>
<p ilvl="1">content</p>
</item>
<item>
<p ilvl="1">content</p>
<list>
<item>
<p ilvl="2">content</p>
<list>
<item>
<p ilvl="3">content</p>
</item>
</list>
</item>
</list>
</item>
</list>
</item>
<item>
<p ilvl="1">content</p>
<list>
<item>
<p ilvl="2">content</p>
等
属性ilvl是列表级别;它是一个从零开始的索引。
我尝试适应 https://stackoverflow.com/a/11117548/1031689 并得到输出:
<rs>
<p ilvl="1"/>
<p ilvl="1">
<p ilvl="2">
<p ilvl="3"/>
</p>
</p>
<p ilvl="1">
<p ilvl="2"/>
<p ilvl="2">
<p ilvl="3"/>
</p>
</p>
<p ilvl="1"/>
<p ilvl="1">
<p ilvl="3"/>
</p>
</rs>
我有两个问题:
- 它不会为任何缺失的级别创建结构(例如,在 1 和 3 之间缺失的级别 2),并且
- 起始参数级别必须与第一个条目匹配(即此处为 1)。如果传0,则嵌套错误。
在我尝试这个之前,我使用的是我自己的 XSLT 1.0 代码,附在下面。
棘手的部分是如何处理嵌套减少,例如 3 级到 1 级:
<p ilvl="3">content</p>
<p ilvl="1">content</p>
更新
我尝试在 addList 模板中处理这个问题,因为递归是“展开”的,但它还不太正确;在我的输出中,当它返回到 1 级时,正在插入一个新列表,但如果我纠正了这一点,我会删除最后 3 个内容项......如果有人能解决这个问题,我会印象深刻:-)
是的,我知道我的代码要复杂得多,所以如果对上面的 for-each-group 方法有任何简单的解决方法,最好有建议。
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<!-- works, except makes new list for siblings -->
<xsl:template name="listSection">
<xsl:param name="last-level">-1</xsl:param>
<xsl:param name="items"/>
<xsl:variable name="currentItem" select="$items[1]"/>
<xsl:variable name="currentLevel">
<xsl:value-of select="number($currentItem/@ilvl)"/>
</xsl:variable>
<xsl:variable name="nextItems" select="$items[position() > 1]"/>
<xsl:choose>
<xsl:when test="$currentLevel = $last-level">
<!-- just add an item -->
<xsl:call-template name="addItem">
<xsl:with-param name="currentItem" select="$currentItem"/>
<xsl:with-param name="nextItems" select="$nextItems"/>
</xsl:call-template>
<!-- that handles next level higher case, and level same case-->
<!-- level lower is handled is addList template-->
</xsl:when>
<xsl:when test="$currentLevel > $last-level">
<xsl:call-template name="addList">
<xsl:with-param name="currentLevel" select="$last-level"/>
<xsl:with-param name="nextItems" select="$items"/> <!-- since haven't handled current item yet -->
</xsl:call-template>
</xsl:when>
<xsl:otherwise> this level < last level: should not happen?</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="addItem">
<xsl:param name="currentItem"/>
<xsl:param name="nextItems"/>
<xsl:variable name="currentLevel">
<xsl:value-of select="number($currentItem/@ilvl)"/>
</xsl:variable>
<item>
<xsl:apply-templates select="$currentItem"/>
<!-- is the next level higher?-->
<xsl:if test="(count($nextItems) > 0) and
(number($nextItems[1]/@ilvl) > $currentLevel)">
<!-- insert list/item to the necessary depth-->
<xsl:call-template name="addList">
<xsl:with-param name="currentLevel" select="$currentLevel"/>
<xsl:with-param name="nextItems" select="$nextItems"/>
</xsl:call-template>
</xsl:if>
</item>
<!-- next level same-->
<xsl:if test="(count($nextItems) > 0) and
(number($nextItems[1]/@ilvl) = $currentLevel)">
<xsl:call-template name="addItem">
<xsl:with-param name="currentItem" select="$nextItems[1]"/>
<xsl:with-param name="nextItems" select="$nextItems[position() > 1]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="addList">
<xsl:param name="currentLevel">-1</xsl:param>
<xsl:param name="nextItems"/>
<xsl:variable name="targetLevel">
<xsl:value-of select="number($nextItems[1]/@ilvl)"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$targetLevel - $currentLevel > 1">
<!-- interpolate -->
<list>
<xsl:variable name="stuff">
<item>
<xsl:call-template name="addList">
<xsl:with-param name="currentLevel" select="$currentLevel+1"/>
<xsl:with-param name="nextItems" select="$nextItems"/>
</xsl:call-template>
</item>
</xsl:variable>
<xsl:copy-of select="$stuff"/>
<xsl:variable name="currentPos" select="count(msxsl:node-set($stuff)//p)" />
<xsl:variable name="ascentLevel">
<xsl:value-of select="number($nextItems[$currentPos]/@ilvl)"/>
</xsl:variable>
<xsl:variable name="ascentItems" select="$nextItems[position() > $currentPos]"/>
<xsl:variable name="aftertargetLevel">
<xsl:value-of select="number($ascentItems[1]/@ilvl)"/>
</xsl:variable>
<xsl:if test="(count($ascentItems) > 1) and
($aftertargetLevel - $currentLevel = 1)">
<xsl:call-template name="listSection">
<xsl:with-param name="last-level" select="$currentLevel"/>
<xsl:with-param name="items" select="$ascentItems"/>
</xsl:call-template>
</xsl:if>
</list>
</xsl:when>
<xsl:when test="$targetLevel - $currentLevel = 1">
<!-- insert real item -->
<xsl:variable name="stuff">
<list>
<xsl:call-template name="addItem">
<xsl:with-param name="currentItem" select="$nextItems[1]"/>
<xsl:with-param name="nextItems" select="$nextItems[position() > 1]"/>
</xsl:call-template>
</list>
</xsl:variable>
<!-- might be items on the way out -->
<xsl:copy-of select="$stuff"/>
<xsl:variable name="currentPos" select="count(msxsl:node-set($stuff)//p)" />
<xsl:variable name="ascentLevel">
<xsl:value-of select="number($nextItems[$currentPos]/@ilvl)"/>
</xsl:variable>
<xsl:variable name="ascentItems" select="$nextItems[position() > $currentPos]"/>
<xsl:variable name="aftertargetLevel">
<xsl:value-of select="number($ascentItems[1]/@ilvl)"/>
</xsl:variable>
<xsl:if test="(count($ascentItems) > 1) and
($aftertargetLevel - $currentLevel = 1)">
<xsl:call-template name="listSection">
<xsl:with-param name="last-level" select="$currentLevel"/>
<xsl:with-param name="items" select="$ascentItems"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<!--should not happen!-->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body">
<xsl:call-template name="listSection">
<xsl:with-param name="items" select="*"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
【问题讨论】:
-
在您引用的帖子中,有 1.0 和 2.0 的解决方案,它们有很大的不同。如果您受限于 XSLT 1.0,您需要这样说,因为这类问题在 2.0 中要容易得多。
-
不限于 1.0(除了我的技能!);事实上,使用 Saxon 9.7.0.7,非常感谢 :-)
-
在标题“使用级别插值”和正文中,“[OP 的尝试] 不会为任何缺失的级别创建结构(例如,在 1 和 3 之间缺失的级别 2)”。这对我来说很清楚。我会提供一个解决方案,但您确实需要一个 XSLT2 解决方案,我对 XSLT2 的了解不如 1。
-
正如弗林所说,需求一直存在。抱歉,您没有注意到 Dimitre,无论如何感谢您的努力!
标签: xslt