【问题标题】:XSLT transform merging valuesXSLT 转换合并值
【发布时间】:2020-02-11 20:20:59
【问题描述】:

我有一个格式如下的 XML 输入文档:

<Label>
    <Person>
        <Hash>12345</Hash>
        <Id>123123</Id>
        <Firstname>John</Firstname>
        <Lastname>Doe</Lastname>
        <Category>Business</Category>
    </Person>
    <Person>
        <Hash>12345</Hash>
        <Id>456789<Id>
        <Fistname>John</Firstname>
        <Lastname>Doe</Lastname>
        <Category>Information</Category>
    </Person>
</Label>

我想合并 Person 中的所有子节点,从而生成以下输出文档:

<Label>
    <Person>
        <Hash>12345</Hash>
        <Id>123123, 456789</Id>
        <Firstname>John</Firstname>
        <Lastname>Doe</Lastname>
        <Category>Business, Information</Category>
    <Person>
</Label>

所以实际上将 Person 中的所有节点合并为一个。最好将相同的值合并为一个值,但这不是必须的。所以下面的转换也是可以接受的:

<Label>
    <Person>
        <Hash>12345, 12345</Hash>
        <Id>123123, 456789</Id>
        <Firstname>John, John</Firstname>
        <Lastname>Doe, Doe</Lastname>
        <Category>Business, Information</Category>
    <Person>
</Label>

欢迎任何有关如何完成此任务的建议!

最好在 xslt 1.0 中转换

【问题讨论】:

  • 合并是否取决于Person 元素中的任何键值?或者您只是想将所有 Person 元素合并在一起?
  • 只想把所有元素合并在一起,所以没有key值

标签: xml xslt transformation


【解决方案1】:

为了使查找字段值稍微简单一些,您可以定义一个键。

  <xsl:key name="field" match="Person/*" use="local-name()" />

然后,您只需要选择第一个Person 元素的子节点,并为每个子节点使用键来键值...

  <xsl:for-each select="key('field', local-name())">
    <xsl:if test="position() > 1">, </xsl:if>
    <xsl:value-of select="." />
  </xsl:for-each>

试试这个 XSLT,它将重复项留在

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

  <xsl:key name="field" match="Person/*" use="local-name()" />

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Person[1]/*">
    <xsl:copy>
      <xsl:for-each select="key('field', local-name())">
        <xsl:if test="position() > 1">, </xsl:if>
        <xsl:value-of select="." />
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Person[position() > 1]" />
</xsl:stylesheet>

如果您确实想删除重复项,则需要使用 Muenchian Grouping。这意味着声明第二个密钥

 <xsl:key name="fieldAndValue" match="Person/*" use="concat(local-name(), ':', .)" />

要获得不同的值,请像这样更改xsl:for-each

<xsl:for-each select="key('field', local-name())[generate-id() = generate-id(key('fieldAndValue', concat(local-name(), ':', .))[1])]">

也试试这个 XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:key name="field" match="Person/*" use="local-name()" />
  <xsl:key name="fieldAndValue" match="Person/*" use="concat(local-name(), ':', .)" />

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Person[1]/*">
    <xsl:copy>
      <xsl:for-each select="key('field', local-name())[generate-id() = generate-id(key('fieldAndValue', concat(local-name(), ':', .))[1])]">
        <xsl:if test="position() > 1">, </xsl:if>
        <xsl:value-of select="." />
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Person[position() > 1]" />
</xsl:stylesheet>

当然,如果你可以使用 XSLT 2.0,你可以更简单...

<xsl:template match="Person[1]/*">
  <xsl:copy>
    <xsl:value-of select="distinct-values(key('field', local-name()))" separator=", " />
  </xsl:copy>
</xsl:template>

编辑:如果您不能使用xsl:key,则将xsl:for-each(在第一个不删除重复项的XSLT 中)更改为此......

 <xsl:for-each select="//Person/*[local-name() = local-name(current())]">

这会保留重复项。可以在没有密钥的情况下删除重复项,但可能比它的价值太麻烦......

【讨论】:

  • 它可以工作,但是....在 Altova XMLSpy 中它会转换,但在最终产品中,我似乎无法使用“密钥”。我一直在尝试更改 xslt,但到目前为止还没有运气;继续与 match="Person[1]/*" 中的选择进行斗争以在没有键的情况下编写它。所以希望你对此有建议。
  • 我已经对我的问题进行了编辑,以展示如何在没有密钥的情况下进行操作,但您确实应该考虑升级您的“最终产品”,以便它确实允许密钥。 xsl:key 自 1999 年第一次正式发布 XSLT 1.0 以来就已经存在,所以无论你使用什么都必须相当旧.....
  • 感谢所有更新!我什至让它与键值一起工作(第一手犯了一些错误)。一切正常,干得好!
  • 在 xslt 中,如果我想从合并中排除某些子节点,即使值相同,它也会删除重复项,如何排除它们?换句话说,在子节点上进行选择(例如,我总是希望拥有哈希节点的值列表,即使它们相同)。
  • 如果我理解正确,您可以将xsl:for-each 更改为.. &lt;xsl:for-each select="key('field', local-name())[local-name() = 'Hash' or local-name() = 'Id' or generate-id() = generate-id(key('fieldAndValue', concat(local-name(), ':', .))[1])]"&gt;
【解决方案2】:
var x = require('xml-js')

//let xml = '**your xml string**'
let xml = '<Label> <Person> <Hash>12345</Hash>        <Id>123123</Id>        <Firstname>John</Firstname>        <Lastname>Doe</Lastname>        <Category>Business</Category>    </Person>    <Person>        <Hash>12345</Hash>        <Id>456789</Id>        <Firstname>John</Firstname>        <Lastname>Doe</Lastname>       <Category>Information</Category>    </Person></Label>'

jd = JSON.parse(x.xml2json(xml, {compact:true})) //converting xml to json

//Putting all values in first index

jd.Label.Person[0].Hash._text = jd.Label.Person.map((ele)=>{return ele.Hash._text}).join(",")

jd.Label.Person[0].Id._text = jd.Label.Person.map((ele)=>{return ele.Id._text}).join(",")

jd.Label.Person[0].Firstname._text = jd.Label.Person.map((ele)=>{return ele.Firstname._text}).join(",")

jd.Label.Person[0].Lastname._text = jd.Label.Person.map((ele)=>{return ele.Lastname._text}).join(",")

jd.Label.Person[0].Category._text = jd.Label.Person.map((ele)=>{return ele.Category._text}).join(",")

let options = {compact: true, ignoreComment: true};

let all = jd.Label.Person[0]

jd.Label.Person=[] //Deleting all the records

jd.Label.Person.push(all) //Inserting single record contains all

let newXml = x.json2xml(jd, options) //json to xml again

console.log(newXml)
//newXml is the new generated xml

【讨论】:

  • tanmay-shrivastava 感谢您的解决方案,但我正在寻找 xslt 中的解决方案。不过感谢您对此进行调查!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多