XML元素覆盖模式设计与实现

在许多应用程序中,经常需要定义一些基础配置,然后针对特定上下文覆盖某些XML元素。本文提出了一种通用的XML元素覆盖设计模式,并提供了基于此模式的通用XSL样式表,允许合并覆盖XML文件与相应的基础XML结构。

XML覆盖设计模式

该设计模式的核心思想是,覆盖XML重复基础XML的结构,以覆盖基础值并指定新的值。覆盖XML的模式几乎可以与基础XML相同,但每个元素不必要求,以便仅指定需要覆盖的XML树的一部分。此外,每个元素可以有一个可选的额外属性overrideMode,用于指定元素如何覆盖相应的基础元素。这个属性可以是以下三个值之一:

  • delete - 用于指示应从XML树中删除此元素及其所有子元素。
  • update - 用于指示当前基础元素必须用覆盖元素更新,但所有子元素不应更改,除非在XML中进一步明确覆盖。
  • replace - 用于指示完全替换元素及其所有子元素。

通过这个额外的属性,可以轻松覆盖基础XML的任何部分。为了演示其工作原理,考虑一个包含一级和二级元素、属性和文本节点的示例基础XML:

<root> <lvl1 id="elem11" value="val11"> <lvl2 name="elem21" value="val21"> txt21 </lvl2> <lvl2 name="elem22" value="val22"> txt22 </lvl2> txt11 </lvl1> </root>

以下示例覆盖XML展示了如何以不同的方式覆盖基础元素。XML中的注释解释了每个元素是如何被覆盖的:

<root> <lvl1 id="elem11" value="oval11" overrideMode="update"> <lvl2 name="elem21" value="oval21"> otxt21 </lvl2> <lvl2 name="elem22" value="oval22"> otxt22 </lvl2> otxt1 </lvl1> <lvl1 id="elem12" value="oval12"> otxt2 </lvl1> <lvl1 id="elem13" value="oval13(ignored)"> <lvl2 name="elem21" value="oval21"/> <lvl2 name="elem22" value="oval22"/> <lvl2 name="elem23" value="oval23"/> otxt3 </lvl1> <lvl1 id="elem14" overrideMode="delete"/> <lvl1 id="elem15" value="oval12" overrideMode="replace"> <lvl2 name="elem21" value="oval21"/> otxt5 </lvl1> </root>

通用XSLT实现

这种设计模式的一个优点是,它允许创建一个通用的转换模板,适用于大多数情况。它使用了一个名为mergeXSLT模板,可以作为转换的一部分由其他模板使用,或者作为一个独立的XSLT应用覆盖XML到基础XML。

这个XSLT的唯一限制是,每个元素要么是唯一的,或者其第一个属性作为键,可以在其父元素中唯一标识该元素。这是一个相当合理的限制,应该覆盖大多数情况,因为为了允许覆盖XML元素,它们需要有一个键,除非它们是其父元素的唯一子元素,并且第一个元素似乎是定义键的好选择。以下是实现此模式的通用XSLT模板:

<?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>
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485