Reputation: 165
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?
Upvotes: 1
Views: 646
Reputation: 167471
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.
Upvotes: 2
Reputation: 2585
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>
Upvotes: 0