使用XSLT将XML结构转换为另一个XML结构

4
我有一个问题。我有以下的源xml文件:
源xml:
<Container>
  <DataHeader>
    <c id="b" value="TAG" />
    <c id="g" value="Info" /> 
  </DataHeader>
  <Data>
    <Rows>
      <r no="1">
        <c id="b" value="uid1" uid="T.A.uid1" />
        <c id="g" value="uid1|tag1|attr1|somevalue1" />
      </r>
   <r no="1">
        <c id="b" value="uid1" uid="T.A.uid1" />
        <c id="g" value="uid1|tag1|attr2|somevalue2" />
      </r>
      <r no="2">
        <c id="b" value="uid1" uid="T.A.uid1" />
        <c id="g" value="uid1|tag2|attr3|somevalue3" />
      </r>
    <r no="10">
        <c id="b" value="uid2" uid="T.A.uid2" />
        <c id="g" value="uid2|tag1|attr1|somevalue4" />
      </r>
      <r no="11">
        <c id="b" value="uid2" uid="T.A.uid2" />
        <c id="g" value="uid2|tag2|attr3|somevalue5" />
      </r>
   </Rows>
  </Data>
</Container>

具体翻译如下:
元素“c”的ID为“g”,在源XML中非常重要。这是一个连接字符串,其值用“|”分隔。我们需要这些值来生成目标XML。
您可以使用ID为“b”的元素“c”来分离“uid”。 值的示例和解释:
 <c id="g" value="uid1|tag1|attr1|somevalue1" />
 **uid value** | element node | **attribute** | attribute value
 **uid1** | tag1 | **attr1** |somevalue1

所有具有相同“uid”的元素必须聚合成一个单独的“TestTag”元素(请参见目标xml)。 具有相同父元素(例如“tag1”)的所有属性(attr1,attr2)需要添加到一个元素中。 我只能使用xslt(xpath)1.0。 转换后,目标xml文件应该如下所示。
<Container>
 <TestTag>
    <object UID="T.A.uid1" Name="uid1"/>
    <tag1 attr1="somevalue1" attr2="somevalue2"/>
    <tag2 attr3="*somevalue3"/>
 </TestTag>
 <TestTag>
    <Iobject UID="T.A.uid2" Name="uid2"/>
    <tag1 attr1="somevalue4" />
    <tag2 attr3="somevalue5"/>
 </TestTag>
</Container>

将源XML转换为目标XML的可能解决方案是什么?我尝试了几种方法,但现在卡住了。

+1 很好的第一次提问。欢迎来到 Stack Overflow! - Tomalak
1个回答

3

这并不是非常困难,但由于广泛(但必要)使用了嵌套的substring-before()substring-after(),令人费解。

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <!-- index <c> nodes by their @id + "uid value" -->
  <xsl:key name="kObject"    match="r/c" use="
    concat(@id, '|', @value)
  " />
  <!-- index <c> nodes by their @id + "uid value" -->
  <xsl:key name="kTagByUid"  match="r/c" use="
    concat(@id, '|', substring-before(@value, '|'))
  " />
  <!-- index <c> nodes by their @id + "uid value" + "tag name" -->
  <xsl:key name="kTagByName" match="r/c" use="
    concat(@id, '|',
      substring-before(
        @value, 
        substring-after(substring-after(@value, '|'), '|')
      )
    )
  " />

  <xsl:variable name="vTagId"  select="/Container/DataHeader/c[@value='TAG'][1]/@id" />
  <xsl:variable name="vInfoId" select="/Container/DataHeader/c[@value='Info'][1]/@id" />

  <!-- processing starts here -->
  <xsl:template match="Container">
    <xsl:copy>
      <!-- apply templates to unique <c @id=$vTagId> tags -->
      <xsl:apply-templates mode="tag" select="
        Data/Rows/r/c[@id=$vTagId][
          generate-id()
          =
          generate-id(key('kObject', concat(@id, '|', @value))[1])
        ]
      " />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="c" mode="tag">
    <TestTag>
      <object UID="{@uid}" name="{@value}" />
      <!-- apply templates to unique <c @id="g"> tags -->
      <xsl:apply-templates mode="info" select="
        key('kTagByUid', concat($vInfoId, '|', @value))[
          generate-id()
          =
          generate-id(
            key(
              'kTagByName', 
              concat(@id, '|', 
                substring-before(
                  @value, 
                  substring-after(substring-after(@value, '|'), '|')
                )
              )
            )[1]
          )
        ]
      " />
    </TestTag>
  </xsl:template>

  <xsl:template match="c" mode="info">
    <!-- select 'uid1|tag1|' - it's the key to kTagByName -->
    <xsl:variable name="key"  select="substring-before(@value, substring-after(substring-after(@value, '|'), '|'))" />
    <!-- select 'tag1' - it's the element name -->
    <xsl:variable name="name" select="substring-before(substring-after($key, '|'), '|')" /> 

    <xsl:element name="{$name}">
      <xsl:for-each select="key('kTagByName', concat(@id, '|', $key))">
        <!-- select 'attr1|somevalue1' - it's the attribute definition -->
        <xsl:variable name="attrDef" select="substring-after(@value, $key)" />
        <!-- create an attribute -->
        <xsl:attribute name="{substring-before($attrDef, '|')}">
          <xsl:value-of select="substring-after($attrDef, '|')" />
        </xsl:attribute>
      </xsl:for-each>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

生成:
<Container>
  <TestTag>
    <object UID="T.A.uid1" name="uid1" />
    <tag1 attr1="somevalue1" attr2="somevalue2"></tag1>
    <tag2 attr3="somevalue3"></tag2>
  </TestTag>
  <TestTag>
    <object UID="T.A.uid2" name="uid2" />
    <tag1 attr1="somevalue4"></tag1>
    <tag2 attr3="somevalue5"></tag2>
  </TestTag>
</Container>

请注意,此处不考虑重复属性定义。如果您同时拥有uid1|tag1|attr1|somevalue1和稍后的uid1|tag1|attr1|othervalue1,那么您最终将得到一个属性:attr1="othervalue1",因为在中两者都会被处理,而后者胜出(即最终输出)。
当然也可以解决这个问题,但需要添加一个键和一个Muenchian分组,这里将其留给读者作为练习。呵呵。 ;)

你好Tomalak, 感谢您的帮助。您的解决方案非常好。 但是现在我有另一个问题。 在源xml中还有一个DataHeader元素。我们不想匹配id的值,因为这是生成的,id和value的组合每次可能都不同。 我们的解决方案是使用xsl:key <xsl:key name="getHeaderValue" match="Container/DataHeader/c" use="@value"/>通过值获取id。 我创建了两个变量来通过值获取id('b'和'g')。例如:<xsl:variable name="tagNameId" select="key('getHeaderValue', 'TAG')/@id"/> - TripleJ
问题在于您的示例中,在xsl:key和模板匹配中都进行了id匹配,而我无法在xsl:key或模板匹配中使用变量。 <xsl:template match="c[@id=$tagNameId]"> 不可行。我们如何通过上述解决方案获得相同的结果,或者我们必须重写整个xslt才能使其工作? - TripleJ
@TripleJ:这需要对样式表进行小改动,没有太大变化,特别是因为代码已经有些“混乱”了。我已经从固定匹配表达式改为了模板模式,并增加了键值。请查看答案的差异:http://stackoverflow.com/posts/2282801/revisions - Tomalak
@Tomalak:感谢您的快速回复!它有效。这段代码在性能方面是否高效?“混乱”是什么意思? - TripleJ
@TripleJ:这是一种名为“Muenchian分组”的技术。网络上已经有很多关于它的文章。我也在这里写了自己的解释:https://dev59.com/kUfSa4cB1Zd3GeqPAtth#955527(请查看我的回答下部的解释)。 - Tomalak
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接