【问题标题】:Transform xml structure to another xml structure with xslt使用 xslt 将 xml 结构转换为另一个 xml 结构
【发布时间】:2011-01-17 21:53:15
【问题描述】:

我有一个问题。我有以下源 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>

id 为“g”的元素“c”在源 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”的所有元素聚合为 1 个单个“TestTag”元素(请参阅目标 xml)。 需要将具有相同父元素(例如“tag1”)的所有属性(attr1、attr2)添加到 1 个元素。 我只能使用 xslt(xpath) 1.0。

转换后的目标 xml 文件应如下所示。

xsl转换后的目标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!

标签: xml xslt


【解决方案1】:

这并不完全困难,但由于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",因为在&lt;xsl:for-each&gt; 中都轮到他们,而后一个获胜(即最终出现在输出中) .

也可以满足这一点,它需要一个键和一个 Muenchian 分组,我将把它作为练习留给读者。呵呵。 ;)

【讨论】:

  • 您好 Tomalak,感谢您的帮助。您的解决方案效果很好。但现在我有另一个问题。在源 xml 中还有一个 DataHeader 元素。我们不想匹配 id 的值,因为这是生成的,并且 id 和 value 的组合每次都可能不同。我们的解决方案是使用 xsl:key 按值获取 id。我制作了两个变量来按值获取id('b'和'g')。一个例子:
  • 问题是,在您的示例中,您在 xsl:key 和模板匹配中的 id 匹配,我无法使用 xsl:key 或模板匹配中的变量。
  • @TripleJ:这需要对样式表做些小改动。没有什么戏剧性的,尤其是因为代码已经“混乱”了。我已经从固定匹配表达式更改为模板模式,并增加了键。查看答案的差异:stackoverflow.com/posts/2282801/revisions
  • @Tomalak:感谢您的快速回复!有用。这段代码在性能方面是否有效?“混乱”到底是什么意思。
  • @TripleJ:性能应该还可以,至少我不知道如何才能更快。我所说的“凌乱”是指你很容易看到它就头疼。即使按照 XSLT 标准,也很难理解。大部分复杂性来自这样一个事实,即您有数据应该位于单独的属性/元素中,这些数据在分隔的字符串中混合在一起。字符串处理是 XSLT (1.0) 真正不擅长的事情,如果您有任何机会更改输入 XML,请务必这样做。
猜你喜欢
  • 2011-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-31
  • 2021-11-09
  • 1970-01-01
  • 2021-09-06
相关资源
最近更新 更多