【问题标题】:XML rearrange and group element nodes XSL 1.0XML 重新排列和分组元素节点 XSL 1.0
【发布时间】:2021-02-01 10:18:01
【问题描述】:

我正在为一项简单的任务而苦苦挣扎。以下 XML 文件

<Root>
    <Row>
        <ConceptID>1</ConceptID>
        <Concept>may be empty</Concept>
        <TermID>2481</TermID>
        <Term>screened room</Term>
        <Language>EN</Language>
        <Usage>forbidden</Usage>
        <StatusLanguage>new</StatusLanguage>
        <Source>HEKT385057</Source>
    </Row>
    <Row>
        <ConceptID>1</ConceptID>
        <Concept>may be empty</Concept>
        <TermID>6551</TermID>
        <Term>shielded room</Term>
        <Language>EN</Language>
        <Usage>allowed</Usage>
        <StatusLanguage>new</StatusLanguage>
        <Source>EKT-TD</Source>
    </Row>
    <Row>
        <ConceptID>1</ConceptID>
        <Concept>may be empty</Concept>
        <TermID>6552</TermID>
        <Term>unverseuchter Raum</Term>
        <Language>DE</Language>
        <Usage>allowed</Usage>
        <StatusLanguage>new</StatusLanguage>
        <Source>EKT-40</Source>
    </Row>
    <Row>
        <ConceptID>2</ConceptID>
        <Concept>may be also empty</Concept>
        <TermID>2482</TermID>
        <Term>low-pressure ventilator</Term>
        <Language>EN</Language>
        <Usage>allowed</Usage>
        <StatusLanguage>new</StatusLanguage>
        <Source>Birgit</Source>
    </Row>
    <Row>
        <ConceptID>2</ConceptID>
        <Concept>may be also empty</Concept>
        <TermID>2483</TermID>
        <Term>LP ventilator</Term>
        <Language>EN</Language>
        <Usage>allowed</Usage>
        <StatusLanguage>new</StatusLanguage>
        <Source>HEKT385057</Source>
    </Row>
...
</Root>

我希望转换为具有以下结构和分组(ConceptID)的新 XML 文件:

<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <NewConcept>
      <ConceptID>1</ConceptID>
      <Concept>may be empty</Concept>
      <TermG>
         <TermID>6551</TermID>
         <Term>shielded room</Term>
         <Language>EN</Language>
         <Usage>allowed</Usage>
         <StatusLanguage>new</StatusLanguage>
         <Source>EKT-TD</Source>
      </TermG>
      <TermG>
         <TermID>6552</TermID>
         <Term>unverseuchter Raum</Term>
         <Language>DE</Language>
         <Usage>allowed</Usage>
         <StatusLanguage>new</StatusLanguage>
         <Source>EKT-40</Source>
      </TermG>
      <TermG>
         <TermID>2481</TermID>
         <Term>screened room</Term>
         <Language>EN</Language>
         <Usage>forbidden</Usage>
         <StatusLanguage>new</StatusLanguage>
         <Source>HEKT385057</Source>
      </TermG>
   </NewConcept>
   <NewConcept>
      <ConceptID>2</ConceptID>
      <Concept>may be also empty</Concept>
      <TermG>
         <TermID>2482</TermID>
         <Term>low-pressure ventilator</Term>
         <Language>EN</Language>
         <Usage>allowed</Usage>
         <StatusLanguage>new</StatusLanguage>
         <Source>Birgit</Source>
      </TermG>
      <TermG>
         <TermID>2483</TermID>
         <Term>LP ventilator</Term>
         <Language>EN</Language>
         <Usage>allowed</Usage>
         <StatusLanguage>new</StatusLanguage>
         <Source>HEKT385057</Source>
      </TermG>
   </NewConcept>
...
</Root>

我当前的 XSL 文件只是将标签复制到所需的结构而不是内容

    <xsl:key name="concept" match="Row" use="ConceptID" />
     <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="Row[generate-id(.)=generate-id(key('concept',ConceptID)[1])]">
            <xsl:sort select="ConceptID" data-type="number"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Row">
        <NewConcept>
            <xsl:apply-templates select="ConceptID" />
            <xsl:apply-templates select="Concept" />
            <xsl:for-each select="key('concept', ConceptID)">
            <xsl:sort select="Usage"/>
                <TermG>     
                    <xsl:apply-templates select="TermID" />
                    <xsl:apply-templates select="Term" />
                    <xsl:apply-templates select="Language" />
                    <xsl:apply-templates select="Usage" />
                    <xsl:apply-templates select="StatusLanguage" />
                    <xsl:apply-templates select="Source" />
                </TermG>
            </xsl:for-each>
        </NewConcept>
    </xsl:template>

产生:

<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <NewConcept>
      <ConceptID/>
      <Concept/>
      <TermG>
         <TermID/>
         <Term/>
         <Language/>
         <Usage/>
         <StatusLanguage/>
         <Source/>
      </TermG>
      <TermG>
         <TermID/>
         <Term/>
         <Language/>
         <Usage/>
         <StatusLanguage/>
         <Source/>
      </TermG>
      <TermG>
         <TermID/>
         <Term/>
         <Language/>
         <Usage/>
         <StatusLanguage/>
         <Source/>
      </TermG>
   </NewConcept>
...
</Root>

更换


<xsl:apply-templates select="Row[generate-id(.)=generate-id(key('concept',ConceptID)[1])]">
    <xsl:sort select="ConceptID" data-type="number"/>
</xsl:apply-templates>

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

给我正确的输出(结构和内容),但是组出现多次,这取决于组中的 man 元素的方式(例如,三个元素导致同一组的三倍)。我非常感谢能帮助我解决此任务的提示!非常感谢。

【问题讨论】:

  • 请解释您尝试实现的逻辑。为什么您的预期输出只包含ConceptID 的第一组?
  • XML 文件代表一个术语数据库。一个概念由不同的语言或允许和禁止的术语组成,包括多个术语。所以第一个 ID=1 的概念包含三个术语,我想对它们进行分组。 Language、Usage、Status 和 Source 是该特定术语的属性,我想将它们保留在一个名为 TermG 的新标签下
  • 这不能回答我的问题。
  • 对不起,我不明白你的问题。我没有发布完整的 XML 文件。我的输出当然也包含 ID2 和所有后续的,我只是在帖子中省略了它们
  • 好的,现在我还添加了第二个 ID

标签: xml xslt


【解决方案1】:

Muenchian 分组 - 这是您在此处尝试实现的 - 有 2 个部分:

  1. 为每个不同的值创建一个组;
  2. 使用具有相同值的节点填充组。

你在这里做的第一部分几乎是正确的:

<xsl:apply-templates select="Row[generate-id(.)=generate-id(key('concept',ConceptID)[1])]">

我说“几乎”是因为您在匹配任何节点/属性的模板中执行此操作,这没有任何意义。您只想进行一次分组。

OTOH,你没有努力实现第二部分。

以下是您可以简单快速地获得预期结果的方法:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="concept" match="Row" use="ConceptID" />

<xsl:template match="/Root">
    <Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <!-- create a group for each distinct ConceptID -->
        <xsl:for-each select="Row[generate-id()=generate-id(key('concept', ConceptID)[1])]">
            <xsl:sort select="ConceptID" data-type="number"/>
            <NewConcept>
                <xsl:copy-of select="ConceptID | Concept"/>
                <!-- populate the group with rows with the current ConceptID -->
                <xsl:for-each select="key('concept', ConceptID)">
                    <xsl:sort select="Usage"/>
                    <TermG>
                        <xsl:copy-of select="*[not(self::ConceptID or self::Concept)]"/>
                    </TermG>
                </xsl:for-each>
            </NewConcept>
        </xsl:for-each>
    </Root>
</xsl:template>

</xsl:stylesheet>

【讨论】:

  • 感谢您的解决方案。 Muench 的方法对我来说是可行的方法。另一个是简单。并且仅使用单个模板完成。
【解决方案2】:

不要修改身份模板。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" encoding="utf-8" />

    <xsl:key name="kRowByConceptID" match="Row" use="ConceptID" />

     <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Root">
        <xsl:copy>
            <xsl:apply-templates select="Row[
                generate-id() = generate-id(key('kRowByConceptID', ConceptID))
            ]">
                <xsl:sort select="ConceptID" data-type="number"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Row">
        <NewConcept>
            <xsl:apply-templates select="ConceptID" />
            <xsl:apply-templates select="Concept" />
            <xsl:apply-templates select="key('kRowByConceptID', ConceptID)" mode="TermG">
                <xsl:sort select="Usage" />
            </xsl:apply-templates>
        </NewConcept>
    </xsl:template>
    
    <xsl:template match="Row" mode="TermG">
        <TermG>     
            <xsl:apply-templates select="TermID" />
            <xsl:apply-templates select="Term" />
            <xsl:apply-templates select="Language" />
            <xsl:apply-templates select="Usage" />
            <xsl:apply-templates select="StatusLanguage" />
            <xsl:apply-templates select="Source" />
        </TermG>        
    </xsl:template>
</xsl:stylesheet>

生产

<Root>
  <NewConcept>
    <ConceptID>1</ConceptID>
    <Concept>may be empty</Concept>
    <TermG>
      <TermID>6551</TermID>
      <Term>shielded room</Term>
      <Language>EN</Language>
      <Usage>allowed</Usage>
      <StatusLanguage>new</StatusLanguage>
      <Source>EKT-TD</Source>
    </TermG>
    <TermG>
      <TermID>6552</TermID>
      <Term>unverseuchter Raum</Term>
      <Language>DE</Language>
      <Usage>allowed</Usage>
      <StatusLanguage>new</StatusLanguage>
      <Source>EKT-40</Source>
    </TermG>
    <TermG>
      <TermID>2481</TermID>
      <Term>screened room</Term>
      <Language>EN</Language>
      <Usage>forbidden</Usage>
      <StatusLanguage>new</StatusLanguage>
      <Source>HEKT385057</Source>
    </TermG>
  </NewConcept>
  <NewConcept>
    <ConceptID>2</ConceptID>
    <Concept>may be also empty</Concept>
    <TermG>
      <TermID>2482</TermID>
      <Term>low-pressure ventilator</Term>
      <Language>EN</Language>
      <Usage>allowed</Usage>
      <StatusLanguage>new</StatusLanguage>
      <Source>Birgit</Source>
    </TermG>
    <TermG>
      <TermID>2483</TermID>
      <Term>LP ventilator</Term>
      <Language>EN</Language>
      <Usage>allowed</Usage>
      <StatusLanguage>new</StatusLanguage>
      <Source>HEKT385057</Source>
    </TermG>
  </NewConcept>
</Root>

【讨论】:

  • 感谢您的解决方案。你们所取得的成就就是如此迅速地提出了解决方案。使用我的完整数据库进行的测试运行创建了我希望能够实现的目标!而且我喜欢 Muench 方法的应用(我还没有完全理解)
  • @MikeH 我特意创建了一个详细的解决方案,以便各个步骤独立存在。正如迈克尔的解决方案所展示的那样,有多种方法可以压缩代码。如果您想了解有关分组如何工作的更多信息,请阅读an old answer of mine,其中我以 JavaScript 为例对&lt;xsl:key&gt; 进行了解释。
  • 感谢您的解释。而且我喜欢您的解决方案,以了解这些步骤。并感谢您的链接。我觉得我可以抓住它,但是当有新任务出现时又会感到困惑。
  • @MikeH 你会得到它的窍门,一旦你把头缠上它就不难了。
  • @TomaIak,我也这么认为……
【解决方案3】:

这是我的解决方案。对我来说,使用变量比键更容易。希望这对您有所帮助。

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

<xsl:variable name='keyconcept' select='/Root/Row[not(ConceptID=preceding-sibling::Row/ConceptID)]'/>
<xsl:variable name='allconcept' select='/Root/Row'/>

<xsl:template match='/'>
    <Root>
        <xsl:for-each select='$keyconcept'>
            <xsl:variable name='conceptid' select='ConceptID'/>
            <NewConcept>
                <xsl:copy-of select='ConceptID'/>
                <xsl:copy-of select='Concept'/>
                <xsl:for-each select='$allconcept[ConceptID = $conceptid]'>
                    <TermG>
                        <xsl:copy-of select='Language'/>
                        <xsl:copy-of select='Usage'/>
                        <xsl:copy-of select='StatusLanguage'/>
                        <xsl:copy-of select='Source'/>
                    </TermG>
                </xsl:for-each>
            </NewConcept>
        </xsl:for-each>
    </Root>

</xsl:template>
</xsl:stylesheet>

【讨论】:

  • 使用变量进行分组比使用键慢很多。对于小的输入,这不会很明显,对于大的输入,这可能会成为一个问题。
  • 感谢您的快速输入。除了 ConceptID 标记始终包含命名空间 xmlns:xsl 之外,模板可以正常工作。相比之下,使用 Muench 的方法,这里的解决方案需要相当多的时间进行转换
  • Row[generate-id()=generate-id(key('concept', ConceptID)[1])] 让我头疼。
  • FWIW 我基于多达 100,000 个“行”元素进行了几次比较。在一般情况下,Muenchian 分组更快。有时快很多。当键中有很多唯一值时,变量方法的性能确实要差得多。在只有几个唯一值(如 10 或 20)的特殊情况下,变量方法同样快,无论有多少行(甚至多达 100,000 个“行”)。这种情况非常罕见,所以我同意 Muenchian 可能值得头疼:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-27
  • 2013-02-26
  • 2013-06-09
  • 2012-09-03
  • 2011-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多