简体   繁体   中英

XSLT Merge two XML structures

I have two xml structures in two variables which I need to merge. I tried writing an XSLT stylesheet based on different awnsers on stackoverflow, but I was not successful.

The structure of the first one looks like this:

<root>
    <content>
        <text-block>
            <descriptionHead>
                Some description text for the text block head.
            </descriptionHead>
            <description>
                Some description text block text.
            </description>
        </text-block>
        <shortDescription>
            <textHead>
                Example text for the short description head.
            </textHead>
            <textBody>
                Example text for the short description text body.
            </textBody>
        </shortDescription>
        <longDescription>
            <textHead>
                Example text for the long description head.
            </textHead>
            <textBody>
                Example text for the short description text body.
            </textBody>
        </longDescription>
    </content>
</root>

And the second one looks like that:

<root>
    <content>
        <text-block>
            <descriptionHead>
                Some text 1.
            </descriptionHead>
            <description>
                Some text 2.
            </description>
        </text-block>
        <shortDescription>
            <textHead></textHead>
            <textBody></textBody>
        </shortDescription>
        <longDescription>
            <textHead>
                Some text 3.
            </textHead>
            <textBody></textBody>
        </longDescription>
    </content>
</root>

As you can see in the second one there are some missing informations. In the shortDescription there is missing the text for textHead and textBody and in longDescription there is mussing the text for textBody. There could be missing no text, some text or all text. Now I want to take the missing informations out of the first xml structure and copy them into the second one and mark the changes with a div tag.

The output should look like that:

    <root>
    <content>
        <text-block>
            <descriptionHead>
                Some text 1.
            </descriptionHead>
            <description>
                Some text 2.
            </description>
        </text-block>
        <shortDescription>
            <textHead>
                <div class="merged">
                    Example text for the short description head.
                </div>
            </textHead>
            <textBody>
                <div class="merged">
                    Example text for the short description text body.
                </div>
            </textBody>
        </shortDescription>
        <longDescription>
            <textHead>
                Some text 3.
            </textHead>
            <textBody>
                <div class="merged">
                    Example text for the short description text body.
                </div>
            </textBody>
        </longDescription>
    </content>
</root>

I can use XSLT 2.0 for that task. Is it possible to do something like this with XSLT?

Here is an example how you could solve it using XSLT 3.0 (as supported by the latest versions of Saxon 9 and Altova) and exploiting xsl:evaluate ( https://www.w3.org/TR/xslt-30/#dynamic-xpath ) and the path function ( https://www.w3.org/TR/xpath-functions-31/#func-path ):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    exclude-result-prefixes="xs math"
    version="3.0">

    <xsl:param name="doc2-uri" as="xs:string" select="'name-of-first-input-in-questions.xml'"/>
    <xsl:param name="doc2" select="doc($doc2-uri)"/>

    <xsl:mode on-no-match="shallow-copy"/>

    <xsl:template match="*[not(has-children())]">
        <xsl:copy>
            <div class="merged">
                <xsl:evaluate context-item="$doc2" xpath="path() || '/text()'"></xsl:evaluate>
            </div>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Note that while Saxon 9.8 HE supports XSLT 3.0 the xsl:evaluate element is unfortunately only supported in the commercial editions.

If the set of elements you want to merge is limited, it might be clearer to match every element like that explicitly and then just copy over the content from the other file, but if you want a more generic way to achieve something like this, here's one option:

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:local="local"
  exclude-result-prefixes="local xs">

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

  <!-- Parse the other XML file and store it in memory. -->
  <xsl:param name="OTHER" select="doc('input-1.xml')"/>

  <!--
  Given a node in an XML document, get the names of all its ancestor elements
  and the name of the element itself as a sequence of strings.

  For example, for root/content/text-block/descriptionHead, this returns:

    ('root', 'content', 'text-block', 'descriptionHead')
  -->
  <xsl:function name="local:lineage" as="xs:string*">
    <xsl:param name="ctx" as="node()"/>

    <xsl:sequence select="
      for $a in $ctx/ancestor-or-self::* return xs:string(node-name($a))
    "/>
  </xsl:function>

  <!-- Match children of content/* that don't have any text content. -->
  <xsl:template match="content/*/*[not(normalize-space(.))]">
    <xsl:variable name="lineage" select="local:lineage(.)"/>

    <xsl:copy>
      <div class="merged">
        <!--
        In the other XML document, find the element with the same "lineage" as
        the current element and apply the template in this stylesheet that
        match the text node children of that element.

        For example, for root/content/text-block/descriptionHead, this
        apply-templates call applies the template that matches the text inside
        root/content/text-block/descriptionHead in the other XML file.

        In this stylesheet, the matching template is the identity template
        below, which copies elements into the output as is.
        -->
        <xsl:apply-templates select="
          $OTHER/root/content/*/*[deep-equal(local:lineage(.), $lineage)]/text()
        "/>
      </div>
    </xsl:copy>
  </xsl:template>

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

</xsl:stylesheet>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM