XSLT:如果元素不存在则插入元素

时间:2014-10-28 14:18:35

标签: xml xslt

我需要使用简单的配置更新许多xml文件。我遇到的问题 - config元素在我的xml文件中是可选的,它们已经有了一些配置。

所以我想做的事情:

  1. 如果缺少所有预定义的<config/>,请插入element
  2. 将遗失的element插入config标记。如果他们已经在那里 - 原样离开。
  3. 之前的

    <root>
      <config> <!-- this is optional. can be not defined at all -->
        <element2 attr="c"/>
      </config>
    </root>
    

    我想要的是什么

    <root>
      <config> 
        <element1 attr="a"/>
        <element2 attr="b"/> <!-- not override this one, but insert if missing -->
        <element3 attr="c"/>
      </config>
    </root>
    

    所以我的想法是有几个模板,如果它不存在则应用第一步,并在单独的mode内进行第二步。但它没有成功。

    UPD。 我使用xslt 1.0,但我猜测切换到2.0将不是问题。

    预定义的元素是:

    <element1 attr="a"/>
    <element2 attr="b"/>
    <element3 attr="c"/>
    

3 个答案:

答案 0 :(得分:3)

这是一个XSLT样式表:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

<xsl:output indent="yes"/>

<xsl:key name="cfg-els-by-name" match="config/*" use="node-name(.)"/>

<xsl:param name="default">
  <config>
    <element1 attr="a"/>
    <element2 attr="b"/>
    <element3 attr="c"/>
  </config>
</xsl:param>

<xsl:variable name="main-doc" select="/"/>

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

<xsl:template match="root[not(config)]">
  <xsl:copy>
    <xsl:copy-of select="$default/config"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="root/config">
  <xsl:copy>
    <xsl:apply-templates select="* , $default/config/*[not(key('cfg-els-by-name', node-name(.), $main-doc))]">
      <xsl:sort select="local-name(.)"/>
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

我不确定config子元素的顺序是否通过local-name()上的排序来确定。

答案 1 :(得分:2)

您需要进行近似身份转换,并进行一些修改。

首先是类似英语的伪代码:

  • root的模板中,分别处理这两种情况:如果你有一个config元素,则处理它;否则,供应一个。
  • 在'config'模板中,对于每个潜在的子元素,分别处理这两种情况:如果你有这样的元素,则处理它,否则提供这样的元素。

现在,在类似XSLT的伪代码中:

root的模板中,分别处理这两种情况:

<xsl:template match='root'>
  <xsl:copy>
    <xsl:choose>
      <xsl:when test="config">
        <xsl:apply-templates/>
      </
      <xsl:otherwise>
        <config>
          <element1 attr="a"/>
          <element2 attr="b"/> 
          <element3 attr="c"/>
        </config>
      </
    </
  </
</

config的模板中,根据需要提供缺少的元素。如果您有固定的订单,代码可以按顺序遍历它们:

<xsl:template match='config'>
  <xsl:copy>
    <xsl:choose>
      <xsl:when test="element1">
        <xsl:apply-templates select="element1"/>
      </
      <xsl:otherwise>
        <element1 attr="a"/>
      </
    </
    <xsl:choose>
      <xsl:when test="element2">
        <xsl:apply-templates select="element2"/>
      </
      <xsl:otherwise>
        <elementb attr="b"/>
      </
    </
    <!--* more chooses, as needed ... *-->
    <xsl:choose>
      <xsl:when test="elementN">
        <xsl:apply-templates select="elementN"/>
      </
      <xsl:otherwise>
        <elementN attr="N"/>
      </
    </
  </
</

如果订单不受约束,这可能会更简单一些:

<xsl:template match="config">
  <xsl:copy>
    <xsl:apply-templates/>
    <xsl:if test="not(element1)">
      <element1 attr="a"/>
    </
    <xsl:if test="not(element2)">
      <element2 attr="b"/>
    </
    <!--* etc ... *-->
  </
</

除非你做的事情比你表现得更复杂,否则我认为不需要额外的模式。

答案 2 :(得分:2)

恕我直言,你应该从另一端看这个;这会阻止任何现有的config,并使用原始config中的现有值或 - 如果找不到任何值 - 来安装您自己的默认值。

这是 XSLT 1.0 实施:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="cfg" match="config/*" use="local-name()" />

<xsl:variable name="default-cfg">
    <element1 attr="a"/>
    <element2 attr="b"/>
    <element3 attr="c"/>
</xsl:variable>

<xsl:variable name="root" select="/"/>

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

<xsl:template match="root">
    <xsl:copy>
        <config> 
            <xsl:for-each select="exsl:node-set($default-cfg)/*">
                <xsl:call-template name="cfg-element"/>
            </xsl:for-each>
        </config>
        <xsl:apply-templates select="node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="config"/>

<xsl:template name="cfg-element">
    <xsl:variable name="name" select="name()"/>
    <xsl:variable name="default-value" select="@attr"/>
    <xsl:for-each select="$root">
        <xsl:variable name="existing-element" select="key('cfg', $name)"/>
        <xsl:element name="{$name}">
            <xsl:attribute name="attr">
                <xsl:choose>
                    <xsl:when test="$existing-element">
                        <xsl:value-of select="$existing-element/@attr"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$default-value"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </xsl:element>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>