【问题标题】:Update an XML file using xslt使用 xslt 更新 XML 文件
【发布时间】:2015-12-30 02:13:10
【问题描述】:

我有两个 xml 文件。

文件 1 -

   <?xml version="1.0" encoding="UTF-8"?>
    <Root>
        <School>
            <section id="12" name="Apple"/>
            <section id="50" name="Newton"/>
        </School>
        <Students>
            <roll no="111" name="Smith"/>
            <roll no="122" name="Alan"/>
            <roll no="20" name="Bruce"/>
        </Students>
        <Teachers>
            <Math>
                <emp id="55" name="Karen"/>
                <emp id="2" name="David"/>
            </Math>
            <Science>
                <emp id="1" name="Thomas"/>
            </Science>
        </Teachers>
        <Sports>
            <Indoor>
                <Boardgame>
                    <game id="12" name="Chess"/>
                </Boardgame>
                <Arcade>
                    <game id="3" name="Car Racing"/>
                </Arcade>
            </Indoor>
            <Outdoor>
                <Field>
                    <game id="1" name="Football"/>
                    <game id="100" name="Cricket"/>
                </Field>
                <Court>
                    <game id="2" name="Tennis"/>
                </Court>
            </Outdoor>
        </Sports>
    </Root>

文件 2 -

<?xml version="1.0" encoding="UTF-8"?>
<Updates>
    <School>
        <section id="12" name="Orange"/>
    </School>
    <Students>
        <roll no="122" name="Sam"/>
    </Students>
    <Teachers>
        <Math>
            <emp id="300" name="Steve" />
        </Math>
    </Teachers>
    <Sports>
        <Indoor>
            <Boardgame>
                <game id="37" name="Monopoly"/>
            </Boardgame>
            <Boardgame2>
                <game id="36" name="Ludo"/>
            </Boardgame2>
        </Indoor>
        <Outdoor>
            <Field>
                <game id="1" name="Football"/>
                <game id="100" name="Bull Fighting"/>
            </Field>
            <Court>
                <game id="19" name="Badminton"/>
            </Court>
        </Outdoor>
        <Computer>
            <game id="10" name="AOE" />
        </Computer>
    </Sports>
</Updates>

我需要合并文件以获得以下输出。如果 id/no 匹配,file2 中的条目将覆盖 file1 中的条目。新元素将根据需要从 file2 的输出中添加到适当层次结构下。

转换的输出 -

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <School>
        <section id="12" name="Orange"/>
        <section id="50" name="Newton"/>
    </School>
    <Students>
        <roll no="111" name="Smith"/>
        <roll no="122" name="Sam"/>
        <roll no="20" name="Bruce"/>
    </Students>
    <Teachers>
        <Math>
            <emp id="55" name="Karen"/>
            <emp id="2" name="David"/>
            <emp id="300" name="Steve" />
        </Math>
        <Science>
            <emp id="1" name="Thomas"/>
        </Science>
    </Teachers>
    <Sports>
        <Indoor>
            <Boardgame>
                <game id="12" name="Chess"/>
                <game id="37" name="Monopoly"/>
            </Boardgame>
            <Arcade>
                <game id="3" name="Car Racing"/>
            </Arcade>
            <Boardgame2>
                <game id="36" name="Ludo"/>
            </Boardgame2>
        </Indoor>
        <Outdoor>
            <Field>
                <game id="1" name="Football"/>
                <game id="100" name="Bull Fighting"/>
            </Field>
            <Court>
                <game id="2" name="Tennis"/>
                <game id="19" name="Badminton"/>
            </Court>
        </Outdoor>
        <Computer>
            <game id="10" name="AOE" />
        </Computer>     
    </Sports>
</Root>

下面是 XSLT,但它仅适用于更新,不适用于插入。

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

    <xsl:output method="xml" indent="no"/>

    <xsl:template match="/">
        <xsl:apply-templates select="node()">
            <xsl:with-param name="doc-context" select="document('file2.xml')/node()" />
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="node()">
        <xsl:param name="doc-context" />

        <xsl:variable name="id" select="@id" />
        <xsl:variable name="no" select="@no" />

        <xsl:copy>
            <xsl:copy-of select="@*|$doc-context[@id = $id or @no = $no]/@*" />
            <xsl:apply-templates select="node()">
                <xsl:with-param name="doc-context" select="$doc-context/node()" />
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

【问题讨论】:

  • 到目前为止你有没有尝试过?如果您尝试解决您的问题,如果您发布尝试的代码,我们可以提供帮助,谢谢
  • 包括了我迄今为止工作的 XSLT,它适用于基于 ids 或 nos 的更新。但它不处理插入或删除。
  • 您可以使用 XSLT 2.0 吗? 3.0?
  • 另外,订购重要吗?
  • 更改文件如何表示删除?

标签: xml xslt


【解决方案1】:

我稍后会写一个完整的样式表,但现在,我将采用一种方法来解决这个问题......

  1. 使用 XSLT 2.0 或 3.0
  2. 从基本的身份转换开始
  3. 使用空模板,删除 @id 与更新文件中的任何 @id 值匹配的元素(稍后我们将介绍如何测试)。
  4. “可识别元素的父级”模板,即 School、Math 等。如何执行此操作取决于此元素名称列表是固定的还是动态的。
  5. 在上述模板中,从正常处理开始(子级上的 xsl:copy 和 xsl:apply-templates),但还添加(在 xsl:copy 下)更新文件中与路径匹配的元素焦点节点。

您可以使用 xsl:key 和 key() 函数进行第 2 步和第 5 步中的测试。但要注意新手的常见陷阱:2-arity key() 函数具有焦点文档的隐式参数。


更新

怎么样...

<xsl:transform
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:so="http://stackoverflow.com/questions/34522017"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    version="2.0"
    exclude-result-prefixes="so xs">

<xsl:output omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:param name="updates-file" as="xs:string" />
<xsl:strip-space elements="*" />

<xsl:variable name="updates" select="doc($updates-file)" />

<xsl:function name="so:merge-key" as="xs:string">
  <xsl:param name="ele" as="element()" />
  <!-- Updates and Root are at the same for merging purposes. -->
  <xsl:variable name="ele-name" select="local-name($ele[not(self::Updates)][not(self::Root)])" />
  <xsl:value-of select="concat( $ele-name, '!', $ele/@id, $ele/@no)" /> 
</xsl:function>

<xsl:template match="@*|comment()|processing-instruction()|text()">
  <xsl:copy />
</xsl:template>

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

<xsl:template match="/Root">
  <xsl:apply-templates select="." mode="update">
      <xsl:with-param name="peer-updates" select="$updates/Updates" />
  </xsl:apply-templates>
</xsl:template>

<xsl:template match="*[not(@id|@no)]" mode="update">
  <xsl:param name="peer-updates" as="element()*" />
  <xsl:variable name="this-key" select="so:merge-key(.)" />
  <xsl:variable name="compare-set" select="*" as="element()*" />
  <xsl:variable name="merge-other" select="$peer-updates[so:merge-key(.) eq $this-key]/*" as="element()*" />
  <xsl:copy>
    <!-- Process the fluff. -->
    <xsl:apply-templates select="@*|comment()|processing-instruction()|text()" />

    <!-- Now the unchanged orginal elements. -->
    <xsl:apply-templates select="*[not( so:merge-key(.) = $merge-other/so:merge-key(.))]" />

    <!-- Now the updated elements. -->
    <xsl:apply-templates select="*[so:merge-key(.) = $merge-other/so:merge-key(.)]" mode="update">
      <xsl:with-param name="peer-updates" select="$merge-other[so:merge-key(.) = $compare-set/so:merge-key(.)]" />
    </xsl:apply-templates>

    <!-- Now new elements. -->
    <xsl:apply-templates select="$merge-other[ not( so:merge-key(.) = $compare-set/so:merge-key(.))]" />
  </xsl:copy>
</xsl:template>

<xsl:template match="*[@id|@no]" mode="update">
  <xsl:param name="peer-updates" as="element()*" />
  <xsl:variable name="this-key" select="so:merge-key(.)" />
  <xsl:variable name="merge-other" select="$peer-updates[so:merge-key(.) eq $this-key]" as="element()?" />
  <xsl:copy-of select="if ($merge-other) then $merge-other else ." />
</xsl:template>

</xsl:transform>

注意事项

  1. 样式表参数updates-file 指定更新文档的URI。传入这个实际参数值。
  2. 我采用了与上述方法不同的方向。这是因为起初我认为 @id 将是一个唯一的文档范围的键,但从您的示例文档来看,情况似乎并非如此。因此,我使用了合并范式。

更新 2

OP 已要求更改订购规则。这是一个快速而肮脏的更改,强制执行指定的排序规则。将两个以 cmets Now the unchanged original elements.Now the updated elements. 为首的序列构造函数替换为这个 ...

<!-- For the original elements, both unchanged and to be updated. -->
<xsl:for-each select="*">
  <xsl:choose>
    <xsl:when test="so:merge-key(.) = $merge-other/so:merge-key(.)">
      <xsl:apply-templates select="." mode="update">
        <xsl:with-param name="peer-updates" select="$merge-other[so:merge-key(.) = $compare-set/so:merge-key(.)]" />
      </xsl:apply-templates>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select="." />
    </xsl:otherwise>
  </xsl:choose>
</xsl:for-each>

一般xsl:for-each 是丑陋和糟糕的。这是一个 xslt 反模式。如果这是我的生产代码,并且我有更多时间考虑它,我会改用模板匹配机制。但无论如何,这是一个快速而肮脏的解决方案。

【讨论】:

  • 谢谢肖恩,这行得通!但正如您在前面的问题中指出的那样,订购确实会受到影响。在大型 xml 文件中,排序变得混乱。有没有办法保持相同的顺序?
  • 你们的订购规则是什么?
  • 排序应该保持不变,如在 file1 中。新添加的节点可以作为最后一个兄弟节点添加。
猜你喜欢
  • 2019-07-28
  • 1970-01-01
  • 1970-01-01
  • 2019-04-11
  • 2021-03-21
  • 1970-01-01
  • 2021-04-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多