I have default configuration items in an XML document as follows:
<ProgramConfig>
<Fragment xml:lang="en" name="TargetSector">fragment/target_sector.xdp</Fragment>
<Fragment xml:lang="fr" name="TargetSector">fragment/target_sector_fr.xdp</Fragment>
<MasterTemplate xml:lang="en">master/default_en.xdp</MasterTemplate>
<MasterTemplate xml:lang="fr">master/default_fr.xdp</MasterTemplate>
</ProgramConfig>
Specific programs can override the default configuration, for example:
<ProgramConfig>
<Fragment xml:lang="en" name="TargetSector">fragment/1-5ABQ/target_sector.xdp</Fragment>
<MasterTemplate xml:lang="fr">master/default_fr_1-5ABQ.xdp</MasterTemplate>
</ProgramConfig>
I need to merge the XML documents, so that the output becomes:
<ProgramConfig>
<Fragment xml:lang="en" name="TargetSector">fragment/1-5ABQ/target_sector.xdp</Fragment>
<Fragment xml:lang="fr" name="TargetSector">fragment/target_sector_fr.xdp</Fragment>
<MasterTemplate xml:lang="en">master/default_en.xdp</MasterTemplate>
<MasterTemplate xml:lang="fr">master/default_fr_1-5ABQ.xdp</MasterTemplate>
</ProgramConfig>
If the program specific XML has an element with same name and matching attributes as the default XML, then it should replace the value in the output document.
The XML is pretty flat - it is always a set of elements under the ProgramConfig root with no further child elements. Each element defines a file system path to an asset.
Is there a way to do this using XSLT. I tried using the document function, but I'm not sure how to match against an element and all the attributes.
Using XSLT 3.0 you could solve it using for-each-group with a composite grouping key using the node name and all the attributes:
<?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:output indent="yes"/>
<xsl:template name="xsl:initial-template">
<ProgramConfig>
<xsl:for-each-group select="doc('defaultConfig.xml')/ProgramConfig/*,
doc('overrideConfig.xml')/ProgramConfig/*"
group-by="node-name(.), sort(@*, function($a) { name($a) })" composite="yes">
<xsl:copy-of select="if (current-group()[2]) then current-group()[2] else current-group()[1]"/>
</xsl:for-each-group>
</ProgramConfig>
</xsl:template>
</xsl:stylesheet>
With XSLT 2.0 it is a bit more difficult to construct single grouping key based on name and all attribute values:
<?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"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs math mf"
version="2.0">
<xsl:output indent="yes"/>
<xsl:function name="mf:sort" as="node()*">
<xsl:param name="input-nodes" as="node()*"/>
<xsl:perform-sort select="$input-nodes">
<xsl:sort select="name()"/>
</xsl:perform-sort>
</xsl:function>
<xsl:template name="main">
<ProgramConfig>
<xsl:for-each-group select="doc('defaultConfig.xml')/ProgramConfig/*,
doc('overrideConfig.xml')/ProgramConfig/*"
group-by="string-join((string(node-name(.)), mf:sort(@*)), '|')">
<xsl:copy-of select="if (current-group()[2]) then current-group()[2] else current-group()[1]"/>
</xsl:for-each-group>
</ProgramConfig>
</xsl:template>
</xsl:stylesheet>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With