XSLT转换兄弟姐妹的孩子

问题描述:

我试图做一些棘手的XSLT 我有一个包含兄弟姐妹的大名单,并根据我想将它们转换为孩子名字的平整的xml 我转化的一般规则是:XSLT转换兄弟姐妹的孩子

  1. 如果标签/名称为“块”,然后打开与标记/值“块”元素作为
  2. 如果标签/名称是“BLOCK_END”关闭“BLOCK”元件(
  3. 在属性所有其他情况下创建一个元素标记/名称,放置标记/值并立即关闭它

因此对于下面的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我越来越近,但仍然不能令人信服。

+0

您可以使用XSLT 2.0处理器像Saxon 9,Altova,XmlPrime? –

+0

@MartinHonnen可悲的是,我坚持使用核心XSLT 1.0 – DannyBoy

+0

而不是在BLOCK_END中“做任何事”,我会尝试像'''',因为您希望从当前的递归中返回,然后继续使用剩余的标记。 – BitTickler

的问题来自于一个事实,即递归调用模板2个服务宗旨(1太多):

  1. 一个接近尾声继续光标移动到输入标签元素。
  2. 处理输出的嵌套级别。

为了这个工作,有必要从递归函数(模板)“返回”当前输出状态和“迭代”状态。

在功能语言中,可以证明这一点,例如,用以下短代码模拟情况。

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> 
+0

许多公平点 - 特别是评论中的点。感谢你的建议,我感觉我越来越近了。我在“END_BLOCK”之后调用模板时有怀疑。整个过程就是“退出递归调用”,这样 xsl:element>是不可思议的。如果我在“END_BLOCK”之后再次进行递归调用,则在关闭元素之前遍历整个文档。请检查我的更新说明 – DannyBoy

+0

@DannyBoy是的,你不想输入另一个递归。但是你想以某种方式推进''.''。也许你需要切换到'' ......“完全构造。如果没有其他办法做到这一点。该参数然后包含例如以下兄弟姐妹左右。 – BitTickler

+0

花了我超过预期,但我设法找到解决方案。在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> 
+0

“*必须扭曲原始的xml *”这使得它成为一个完全不同的问题。当我们在这里回答XSLT问题时,我们假设给出了输入XML模式。如果您确实有能力根据自己的喜好对其进行修改,为什么不让它实际上易于处理? –

+0

“不得不twim原来的xml”并不意味着“我是那个生成xml并且可以按照我的喜好制作结构的人”。我的意思是说,我的原始XML更复杂,并有更多的数据。在这个SO问题中,我已经过滤了xml以捕捉我的问题的本质。幸运的是,“封闭标识符”是我可以得到的,但是我最初忽略它,因为我(错误地)认为它是无关紧要的 – DannyBoy