Click here to Skip to main content
15,861,172 members
Articles / Programming Languages / XSLT
Article

Design Pattern for Overriding XML

Rate me:
Please Sign up or sign in to vote.
4.83/5 (6 votes)
28 Oct 2008CPOL3 min read 34.7K   15   12
A design pattern on how to override elements in a base XML.

Introduction

With XML being widely used for defining various system configurations, there is sometimes a need to be able to define some base configuration and then override certain XML elements in separate configuration files for each specific context.

The following article presents a generic design pattern for overriding XML elements, and also provides a generic XSL stylesheet based on this pattern that allows merging an overriding XML file with the corresponding base XML structure.

XML Override Design Pattern

The idea behind this design pattern is for the overriding XML to repeat the structure of the base XML to the extent necessary to override the base values and to specify new values to be used. The schema for the overriding XML can be almost the same as for the base XML, except that each element would be not required to enable specifying just a part of the XML tree that is needed for overriding. In addition, each element can have an optional extra attribute overrideMode to specify how the element overrides the corresponding base element. This attribute can assume one of the following three values:

  • delete – this value can be used on any element to indicate that this element including all its child elements should be deleted from the XML tree.
  • update – this value can be used on any non-leaf override element to indicate that the current base element must be updated with the override element, but all child elements should not be changed unless specifically overridden further in the XML. This is driven by the fact that non-leaf elements in the overriding XML could serve a dual purpose: to provide new values for that element, and to provide the structure for its child override elements. Override XML elements that have no children require no extra attribute at all since their very presence would automatically indicate either overriding of the existing elements or adding new elements, respectively.
  • replace – this value can be used on non-leaf elements to completely replace an element including all its child elements.

With this additional attribute, it is possible to easily override any part of the base XML in a pretty straightforward manner. To demonstrate how it works, let us consider a sample base XML that consists of a mix of first and second level elements, attributes, and text nodes, as follows:

XML
<root>
    <lvl1 id="elem11" value="val11">
        <lvl2 name="elem21" value="val21">txt21</lvl2>
        <lvl2 name="elem22" value="val22">txt22</lvl2>txt11</lvl1>
    <lvl1 id="elem12" value="val12">
        <lvl2 name="elem21" value="val21">txt21</lvl2>
        <lvl2 name="elem22" value="val22">txt22</lvl2>txt12</lvl1>
    <lvl1 id="elem13" value="val13">
        <lvl2 name="elem21" value="val21">txt21</lvl2>
        <lvl2 name="elem22" value="val22">txt22</lvl2>txt13</lvl1>
    <lvl1 id="elem14" value="val14">
        <lvl2 name="elem21" value="val21">txt21</lvl2>
        <lvl2 name="elem22" value="val22">txt22</lvl2>txt14</lvl1>
    <lvl1 id="elem15" value="val15">
        <lvl2 name="elem21" value="val21">txt21</lvl2>
        <lvl2 name="elem22" value="val22">txt22</lvl2>txt15</lvl1>
</root>

Now, the following sample override XML shows some examples of how to override different base elements in different ways. The comments in the XML explain how each element is being overridden.

XML
<root>
    <!-- update elem11 and the two child elements respectively -->
    <lvl1 id="elem11" value="oval11" overrideMode="update">
        <lvl2 name="elem21" value="oval21">otxt21</lvl2>
        <lvl2 name="elem22" value="oval22">otxt22</lvl2>otxt1</lvl1>
    <!-- update elem12, since it has no children -->
    <lvl1 id="elem12" value="oval12">otxt2</lvl1>
    <!-- update children of elem13 but not the element itself, 
         since the overrideMode is not specified -->
    <lvl1 id="elem13" value="oval13(ignored)">
        <lvl2 name="elem21" value="oval21"/>
        <lvl2 name="elem22" value="oval22"/>
        <lvl2 name="elem23" value="oval23"/>otxt3</lvl1>
    <!-- delete elem14 -->
    <lvl1 id="elem14" overrideMode="delete"/>
    <!-- replace elem15 with the following element -->
    <lvl1 id="elem15" value="oval12" overrideMode="replace">
        <lvl2 name="elem21" value="oval21"/>otxt5</lvl1>
</root>

Generic XSLT Implementation

One great thing about this design pattern is that it allows creating a generic transformation template that would work for most of the cases. It uses an XSLT template named merge that can be used either by other templates as part of the transformation, or in a standalone XSL to apply the overriding XML to the base XML.

The only limitation this XSLT imposes is that each element would either be one of a kind, or have its first attribute as a key that could uniquely identify the element within its parent element. This is a pretty reasonable limitation that should cover most of the cases, since in order to allow overriding XML elements, they need to have a key, unless they are a single child of their parent of its kind, and the first element seems to be a good choice to define the key. Below is the generic XSLT template that implements this pattern.

XML
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:output indent="yes"/>
  <xsl:param name="overrideFile"/>
  <xsl:template match="/">
    <xsl:call-template name="merge">
      <xsl:with-param name="std" select="."/>
      <xsl:with-param name="ovrd" select="document($overrideFile)"/>
    </xsl:call-template>
  </xsl:template>
  <xsl:template name="merge">
    <xsl:param name="std"/>
    <xsl:param name="ovrd"/>
    <xsl:for-each select="$std/*">
      <xsl:variable name="key" select="@*[1]"/>
      <xsl:variable name="ovr" 
         select="$ovrd/*[local-name() = local-name(current()) 
                 and (not($key) or @*[1] = $key)]"/>
      <xsl:if test="count($ovr) = 0">
        <xsl:copy-of select="."/>
      </xsl:if>
      <xsl:if test="count($ovr) = 1 and (not($ovr/@overrideMode) 
                    or $ovr/@overrideMode != 'delete')">
        <xsl:choose>
          <xsl:when test="count($ovr/*) = 0 or $ovr/@overrideMode = 'update' 
                          or $ovr/@overrideMode = 'replace'">
            <xsl:variable name="current" select="."/>
            <xsl:for-each select="$ovr">
              <xsl:copy>
                <xsl:for-each select="@*[name() != 'overrideMode']|
                                      text()[string-length(normalize-space(.))>0]">
                  <xsl:copy/>
                </xsl:for-each>
                <xsl:choose>
                  <xsl:when test="$ovr/@overrideMode = 'replace'">
                    <xsl:copy-of select="*"/>
                  </xsl:when>
                  <xsl:otherwise>
                    <xsl:call-template name="merge">
                      <xsl:with-param name="std" select="$current"/>
                      <xsl:with-param name="ovrd" select="."/>
                    </xsl:call-template>
                  </xsl:otherwise>
                </xsl:choose>
              </xsl:copy>
            </xsl:for-each>
          </xsl:when>
          <xsl:otherwise>
            <xsl:copy>
              <xsl:for-each select="@*|text()[string-length(normalize-space(.))>0]">
                <xsl:copy/>
              </xsl:for-each>
              <xsl:call-template name="merge">
                <xsl:with-param name="std" select="."/>
                <xsl:with-param name="ovrd" select="$ovr"/>
              </xsl:call-template>
            </xsl:copy>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:if>
    </xsl:for-each>
    <xsl:for-each select="$ovrd/*">
      <xsl:variable name="key" select="@*[1]"/>
      <xsl:if test="count($std/*[local-name() = local-name(current()) 
                    and (not($key) or @*[1] = $key)]) = 0">
        <xsl:copy-of select="."/>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Conclusion

This article illustrates how a common pattern of defining overriding XML can be achieved by using pretty much the same structure as the original XML plus an additional attribute for each element. This pattern can leverage a generic XSLT implementation that can apply the overriding XML to the base XML and transform it into the resulting XML.

History

  • 10/28/2008: The first version of this article has been posted.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
President SIS Consulting, LLC
United States United States
President and CEO of SIS Consulting, LLC – an offshore software development company.
Also a Senior Consultant at Princeton Consultants, Inc. – a US-based IT and Management consulting company.

Comments and Discussions

 
GeneralAttribute order is implementation-dependent Pin
ses4j7-Jul-10 9:24
ses4j7-Jul-10 9:24 
GeneralRe: Attribute order is implementation-dependent Pin
Serghei Sarafudinov7-Jul-10 9:32
Serghei Sarafudinov7-Jul-10 9:32 
QuestionAttribute merge capability? Pin
ses4j7-Jul-10 7:50
ses4j7-Jul-10 7:50 
AnswerRe: Attribute merge capability? Pin
Serghei Sarafudinov7-Jul-10 8:17
Serghei Sarafudinov7-Jul-10 8:17 
GeneralRe: Attribute merge capability? Pin
ses4j7-Jul-10 9:18
ses4j7-Jul-10 9:18 
Generalbugfix for nodes with only an overrideMode attribute Pin
ses4j7-Jul-10 7:36
ses4j7-Jul-10 7:36 
GeneralRe: bugfix for nodes with only an overrideMode attribute Pin
Serghei Sarafudinov7-Jul-10 12:07
Serghei Sarafudinov7-Jul-10 12:07 
GeneralMultiple/Recursive Files [modified] Pin
SoapSeller13-Apr-10 4:22
SoapSeller13-Apr-10 4:22 
GeneralExactly what I was looking for! Pin
Wolfgang Mena-Bruhn7-Feb-09 3:00
Wolfgang Mena-Bruhn7-Feb-09 3:00 
AnswerRe: Exactly what I was looking for! Pin
Serghei Sarafudinov7-Feb-09 9:24
Serghei Sarafudinov7-Feb-09 9:24 
GeneralAhh, I see Pin
Dmitri Nеstеruk2-Nov-08 20:11
Dmitri Nеstеruk2-Nov-08 20:11 
GeneralRe: Ahh, I see Pin
Serghei Sarafudinov4-Nov-08 17:12
Serghei Sarafudinov4-Nov-08 17:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.