XSLT转换兄弟姐妹的孩子
我试图做一些棘手的XSLT 我有一个包含兄弟姐妹的大名单,并根据我想将它们转换为孩子名字的平整的xml 我转化的一般规则是:XSLT转换兄弟姐妹的孩子
- 如果标签/名称为“块”,然后打开与标记/值“块”元素作为
- 如果标签/名称是“BLOCK_END”关闭“BLOCK”元件(
- 在属性所有其他情况下创建一个元素标记/名称,放置标记/值并立即关闭它
因此对于下面的XML:
<message>
<tag>
<name>BLOCK</name>
<value>first</value>
</tag>
<tag>
<name>FOO</name>
<value>BAR</value>
</tag>
<tag>
<name>BLOCK</name>
<value>second</value>
</tag>
<tag>
<name>FOO2</name>
<value>BAR2</value>
</tag>
<tag>
<name>BLOCK_END</name>
</tag>
<tag>
<name>BLOCK_END</name>
</tag>
<tag>
<name>BLOCK</name>
<value>third</value>
</tag>
<tag>
<name>FOO3</name>
<value>BAR3</value>
</tag>
<tag>
<name>BLOCK_END</name>
</tag>
</message>
这是我希望的结果:
<message>
<BLOCK id="first">
<FOO>BAR</FOO>
<BLOCK id="second">
<FOO2>BAR2</FOO2>
</BLOCK>
</BLOCK>
<BLOCK id="third">
<FOO3>BAR3</FOO3>
</BLOCK">
</message>
我用下面的XSLT。这是工作正常,但遗憾的是它完成后encoutering第一BLOCK_END标签
<xsl:template match="/">
<message>
<xsl:apply-templates select="message/tag[1]" />
</message>
</xsl:template>
<xsl:template match="tag">
<xsl:variable name="tagName" select="name"/>
<xsl:variable name="tagValue" select="value"/>
<xsl:choose>
<xsl:when test="$tagName = 'BLOCK'">
<xsl:element name="{$tagName}">
<xsl:attribute name="id">
<xsl:value-of select="$tagValue"/>
</xsl:attribute>
<xsl:apply-templates select="./following-sibling::*[1]" />
</xsl:element>
</xsl:when>
<xsl:when test="$tagName = 'BLOCK_END'">
<!-- DO NOTHING-->
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$tagName}">
<xsl:value-of select="$tagValue"/>
</xsl:element>
<xsl:apply-templates select="./following-sibling::*[1]" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
UPDATE执行:感谢BitTickler我越来越近,但仍然不能令人信服。
的问题来自于一个事实,即递归调用模板2个服务宗旨(1太多):
- 一个接近尾声继续光标移动到输入标签元素。
- 处理输出的嵌套级别。
为了这个工作,有必要从递归函数(模板)“返回”当前输出状态和“迭代”状态。
在功能语言中,可以证明这一点,例如,用以下短代码模拟情况。
type Node =
| Simple of string * string
| Nested of string * string * Node list
let input =
[
Simple ("BLOCK","first")
Simple ("FOO","BAR")
Simple ("BLOCK","second")
Simple ("FOO2","BAR2")
Simple ("BLOCK_END","")
Simple ("FOO3","BAR3")
Simple ("BLOCK_END","")
]
let rec transform (result,remaining) =
match remaining with
| [] -> result,remaining
| x::xs ->
match x with
| Simple (n,v) when n = "BLOCK" ->
let below,remaining' = transform ([],xs)
transform (result @ [Nested(n,v,below)],remaining')
| Simple (n,v) when n = "BLOCK_END" ->
result,xs
| Simple (n,v) ->
transform (result @[x],xs)
transform ([],input)
现在有1个解决方案战略,其工作原理,剩余唯一的问题是,如何运用这个策略XSLT转换。
要启动整个事情,可能应该转换第一个<tag>
元素。在其转换过程中发生递归。
BLOCK_END应该以某种方式从递归中返回,因此当前位置是已知的,因此BLOCK部分可以在稍后恢复。
我最好的猜测,到目前为止是这样的:
<xsl:template match="/">
<xsl:element name="message">
<xsl:apply-templates select="/message/tag[1]" />
</xsl:element>
</xsl:template>
<xsl:template name="nest" match="tag">
<xsl:variable name="tagName" select="name"/>
<xsl:variable name="tagValue" select="value"/>
<xsl:choose>
<xsl:when test="./name='BLOCK'">
<xsl:element name="{$tagName}">
<xsl:attribute name="id">
<xsl:value-of select="$tagValue"/>
</xsl:attribute>
<xsl:apply-templates select="./following-sibling::tag[1]" />
</xsl:element>
<!--TODO: We must continue here with the remaining nodes. But we do not know how many
Nodes the block contained... Our cursor (.) is unaffected by previous recursion. -->
<!--<xsl:apply-templates select="./following-sibling::tag[1]" />-->
</xsl:when>
<xsl:when test="./name='BLOCK_END'">
<!--No nothing-->
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$tagName}">
<xsl:value-of select="$tagValue"/>
</xsl:element>
<xsl:apply-templates select="./following-sibling::tag[1]" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
产生输出:
<message>
<BLOCK id="first">
<FOO>BAR</FOO>
<BLOCK id="second">
<FOO2>BAR2</FOO2>
</BLOCK>
</BLOCK>
</message>
许多公平点 - 特别是评论中的点。感谢你的建议,我感觉我越来越近了。我在“END_BLOCK”之后调用模板时有怀疑。整个过程就是“退出递归调用”,这样 xsl:element>是不可思议的。如果我在“END_BLOCK”之后再次进行递归调用,则在关闭
@DannyBoy是的,你不想输入另一个递归。但是你想以某种方式推进''.''。也许你需要切换到''
花了我超过预期,但我设法找到解决方案。在TODO部分,我查找“关闭”标签并在结束标签后重新初始化下一个兄弟的循环。奇迹般有效。这是一个很酷的问题,如果没有你的帮助,不可能完成它!非常感谢! – DannyBoy
可能会有一个较短的方式,但这种在我看来仍然是最strai ghtforward方法:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="child-item" match="tag[not(name='BLOCK')]" use="generate-id(preceding-sibling::tag[name='BLOCK'][@level=current()/@level][1])" />
<xsl:key name="child-block" match="tag[name='BLOCK']" use="generate-id(preceding-sibling::tag[name='BLOCK'][@level=current()/@level - 1][1])" />
<xsl:template match="/message">
<xsl:variable name="first-pass-rtf">
<xsl:apply-templates select="tag[1]" mode="first-pass" />
</xsl:variable>
<xsl:variable name="first-pass" select="exsl:node-set($first-pass-rtf)/tag" />
<!-- output -->
<message>
<xsl:apply-templates select="$first-pass[name='BLOCK'][@level=1]"/>
</message>
</xsl:template>
<!-- first-pass templates -->
<xsl:template match="tag[name='BLOCK']" mode="first-pass">
<xsl:param name="level" select="0"/>
<tag level="{$level + 1}">
<xsl:copy-of select="*"/>
</tag>
<xsl:apply-templates select="following-sibling::tag[1]" mode="first-pass">
<xsl:with-param name="level" select="$level + 1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="tag" mode="first-pass">
<xsl:param name="level"/>
<tag level="{$level}">
<xsl:copy-of select="*"/>
</tag>
<xsl:apply-templates select="following-sibling::tag[1]" mode="first-pass">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="tag[name='BLOCK_END']" mode="first-pass">
<xsl:param name="level"/>
<xsl:apply-templates select="following-sibling::tag[1]" mode="first-pass">
<xsl:with-param name="level" select="$level - 1"/>
</xsl:apply-templates>
</xsl:template>
<!-- output templates -->
<xsl:template match="tag[name='BLOCK']">
<BLOCK id="{value}">
<xsl:apply-templates select="key('child-item', generate-id()) | key('child-block', generate-id())" />
</BLOCK>
</xsl:template>
<xsl:template match="tag">
<xsl:element name="{name}">
<xsl:value-of select="value"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
这将通过重写你输入作为开始:
<xsl:variable name="first-pass-rtf">
<tag level="1">
<name>BLOCK</name>
<value>first</value>
</tag>
<tag level="1">
<name>FOO</name>
<value>BAR</value>
</tag>
<tag level="2">
<name>BLOCK</name>
<value>second</value>
</tag>
<tag level="2">
<name>FOO2</name>
<value>BAR2</value>
</tag>
<tag level="1">
<name>BLOCK</name>
<value>third</value>
</tag>
<tag level="1">
<name>FOO3</name>
<value>BAR3</value>
</tag>
</xsl:variable>
然后,它变得简单(R),以每tag
到其父块相关联。
最终的解决方案是在BitTickler的理念的延伸:
我不得不twick原始XML使得最终块标签还包含一个标识符(我们用它来搜索相应的END_BLOCK标记为BLOCK标签)
<message>
<tag>
<name>BLOCK</name>
<value>first</value>
</tag>
<tag>
<name>FOO</name>
<value>BAR</value>
</tag>
<tag>
<name>BLOCK</name>
<value>second</value>
</tag>
<tag>
<name>FOO2</name>
<value>BAR2</value>
</tag>
<tag>
<name>BLOCK_END</name>
<value>second</value>
</tag>
<tag>
<name>BLOCK_END</name>
<value>first</value>
</tag>
<tag>
<name>BLOCK</name>
<value>third</value>
</tag>
<tag>
<name>FOO3</name>
<value>BAR3</value>
</tag>
<tag>
<name>BLOCK_END</name>
<value>third</value>
</tag>
</message>
然后诀窍是做的模板END_BLOCK + 1个标签的反复调用(请参阅“神奇”的评论)。没有做“呼叫模板”的好处(只是我身边的一些剩余的尝试和错误),我将恢复到“apply-templates”
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<message>
<xsl:call-template name="transformTag">
<xsl:with-param name="tag" select="message/tag[1]"/>
</xsl:call-template>
</message>
</xsl:template>
<xsl:template name="transformTag">
<xsl:param name="tag"/>
<xsl:variable name="tagName" select="$tag/name"/>
<xsl:variable name="tagValue" select="$tag/value"/>
<xsl:choose>
<xsl:when test="$tagName = 'BLOCK'">
<!-- OPEN A BLOCK, RECURENTLY CALL FOR THE NEXT ELEMENT AND THEN MAKE A RECURENT CALL ONCE AGAIN FOR THE NEXT BLOCK-->
<xsl:element name="{$tagName}">
<xsl:attribute name="id">
<xsl:value-of select="$tagValue"/>
</xsl:attribute>
<xsl:call-template name="transformTag">
<xsl:with-param name="tag" select="$tag/following-sibling::*[1]"/>
</xsl:call-template>
</xsl:element>
<!-- THIS IS WHERE THE MAGIC HAPPENS-->
<xsl:variable name="closingTag" select="$tag/following-sibling::*[name='END_BLOCK' and substring-before(value,'@@')=$tagValue][1]"/>
<xsl:if test="$closingTag/name='END_BLOCK'">
<xsl:variable name="nextTag" select="$closingTag/following-sibling::*[1]"/>
<xsl:if test="$nextTag[name() = 'tag']">
<xsl:call-template name="transformTag">
<xsl:with-param name="tag" select="$nextTag"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:when>
<xsl:when test="$tagName = 'END_BLOCK'">
<!-- DO NOTHING AND EXIT THE RECURENT CALL (THIS CLOSES THE TAG)-->
</xsl:when>
<xsl:otherwise>
<!-- PRINT THE REGULAR TAG AND RECURENTLY CALL FOR THE NEXT TAG-->
<xsl:element name="_{$tagName}">
<xsl:value-of select="$tagValue"/>
</xsl:element>
<xsl:call-template name="transformTag">
<xsl:with-param name="tag" select="$tag/following-sibling::*[1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
“*必须扭曲原始的xml *”这使得它成为一个完全不同的问题。当我们在这里回答XSLT问题时,我们假设给出了输入XML模式。如果您确实有能力根据自己的喜好对其进行修改,为什么不让它实际上易于处理? –
“不得不twim原来的xml”并不意味着“我是那个生成xml并且可以按照我的喜好制作结构的人”。我的意思是说,我的原始XML更复杂,并有更多的数据。在这个SO问题中,我已经过滤了xml以捕捉我的问题的本质。幸运的是,“封闭标识符”是我可以得到的,但是我最初忽略它,因为我(错误地)认为它是无关紧要的 – DannyBoy
您可以使用XSLT 2.0处理器像Saxon 9,Altova,XmlPrime? –
@MartinHonnen可悲的是,我坚持使用核心XSLT 1.0 – DannyBoy
而不是在BLOCK_END中“做任何事”,我会尝试像'''',因为您希望从当前的递归中返回,然后继续使用剩余的标记。 –
BitTickler