【问题标题】:XSLT adding new nodes if they do not exist如果新节点不存在,XSLT 添加新节点
【发布时间】:2013-11-27 13:51:33
【问题描述】:

我在使用 XSLT 检查节点是否存在时遇到了一些问题,如果不存在则将它们添加到文档中。这是我的情况:

输入

<Message>
  <a>123</a>
  <c>456</c>
  <d>789</d>
</Message>

期望的输出

<MsgHead>
  <Document>
    <Message>
     <a>123</a>
     <b>-1</b>
     <c>456</c>
     <d>789</d>
    </Message>
  <Document>
</MsgHead>

我还获得了以下带有“默认值”的静态文件

默认值

<DefaultNodes>
  <a>-1</a>
  <b>-1</b>
  <c>-1</c>
  <d>-1</d>
 </DefaultNodes>

输入文件有不同数量的节点,我需要用缺少的默认节点“完成”它们。节点名称显然不是a、b、c等,而是大约700个不同默认值的不同节点。

这是我迄今为止的尝试 我的 XSLT

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">    
    <xsl:template match="/">
        <MsgHead>
            <Document>
                <Message>
                    <xsl:apply-templates></xsl:apply-templates>
                </Message>
            </Document>
        </MsgHead>
    </xsl:template>

    <xsl:template match="Message">
        <xsl:copy-of select="node()"/>
        <xsl:for-each select="document('default-nodes.xml')/DefaultNodes/*">
            <xsl:choose>
                <xsl:when test="//*[local-name(current())]"> <!-- This is the line giving me trouble -->
                    <!--Node already present, do nothing-->                    
                </xsl:when>
                <xsl:otherwise>
                    <!--Node not in input, add from the defaults file -->
                    <xsl:copy-of select="self::node()"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

这几乎可以工作,但似乎无法找到节点是否存在。我正在使用的当前测试 (//*[local-name(current())]) 似乎无论如何都会返回 true。有人对我如何解决这个问题有任何建议吗?

谢谢!

【问题讨论】:

    标签: xml xslt


    【解决方案1】:

    我正在使用的当前测试 (//*[local-name(current())]) 似乎无论如何都返回 true

    是的,因为(在您正在测试它的上下文中)该测试意味着“如果此模板的当前节点的本地名称不为空,则选择整个 default-nodes.xml 文档中的所有元素,否则什么都不选”。

    我假设default-nodes.xml 定义了您希望结果元素出现的顺序,并且DefaultNodes 下的所有元素都有不同的名称。在这种情况下,如何:

    <xsl:stylesheet version="1.0"
                    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">    
    
        <!-- store a reference to the root of the main input tree  -->
        <xsl:variable name="root" select="/" />
    
        <xsl:template match="/">
            <MsgHead>
                <Document>
                    <Message>
                        <xsl:apply-templates select="document('default-nodes.xml')/DefaultNodes/*"/>
                    </Message>
                </Document>
            </MsgHead>
        </xsl:template>
    
        <xsl:template match="DefaultNodes/*">
            <!-- look for a matching element in the main input tree -->
            <xsl:variable name="sourceNode" select="$root/Message/*[name() = name(current())]" />
            <xsl:choose>
                <!-- if such a node exists, copy it -->
                <xsl:when test="$sourceNode">
                    <xsl:copy-of select="$sourceNode" />
                </xsl:when>
                <!-- else copy the default one -->
                <xsl:otherwise>
                    <xsl:copy-of select="." />
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    </xsl:stylesheet>
    

    这里我将模板应用到 default 元素,而不是 input 元素。

    注意使用变量$root 来保存对主输入树的引用。在DefaultNodes/* 模板中,当前节点来自default-nodes.xml,因此在该上下文中/ 表示该文件的根,而不是主输入树的根。

    【讨论】:

    • 像魅力一样工作,比我的方法优雅得多!非常感谢!
    【解决方案2】:

    可能这不是最简洁的解决方案,但它确实有效。

    顺便说一句,local-name 返回一个没有其命名空间的名称(即,如果有冒号,则在冒号后面的部分)。以防万一您不知道这一点。

    样式表

    <?xml version="1.0" encoding="utf-8"?>
    
    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="/">
        <MsgHead>
            <Document>
                <Message>
                    <xsl:apply-templates/>
                </Message>
            </Document>
        </MsgHead>
    </xsl:template>
    
    <xsl:template match="Message">
        <xsl:variable name="msg" select="."/>
        <xsl:for-each select="document('default-nodes.xml')/DefaultNodes/*">
            <xsl:choose>
               <xsl:when test="$msg/*/name()=current()/name()">
                  <xsl:copy-of select="$msg/*[name()=current()/name()]"/>
               </xsl:when>
               <xsl:otherwise>
                  <xsl:copy-of select="."/>
               </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
    </xsl:stylesheet>
    

    输出

    <?xml version="1.0" encoding="UTF-8"?>
    <MsgHead>
     <Document>
      <Message>
         <a>123</a>
         <b>-1</b>
         <c>456</c>
         <d>789</d>
      </Message>
     </Document>
    </MsgHead>
    

    【讨论】:

    • 感谢您的回复。我已经得到了@IanRoberts 的解决方案,但还是决定试试你的解决方案。您的解决方案的以下部分:'$msg/*/name()=current()/name()' 给我错误 'location step expected'。
    • 它绝对适用于 Saxon 9.1 和 XSLT 2.0,所以这可能是您的 XSLT 处理器的特性?但由于 Ian 的解决方案运行良好,因此不要花太多时间考虑这一点。干杯!
    【解决方案3】:

    这个 XSLT 1.0 样式表 ...

    <xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:esl="urn:schemas-microsoft-com:xslt" 
      exclude-result-prefixes="xsl esl" >
    <xsl:output indent="yes" omit-xml-declaration="yes" />
    <xsl:strip-space elements="*" />    
    
    <xsl:key name="kByName" match="*" use="local-name()" />  
    
    <xsl:variable name="DefaultNodes">
     <DefaultNodes>
      <a>-1</a>
      <b>-1</b>
      <c>-1</c>
      <d>-1</d>
     </DefaultNodes>  
    </xsl:variable> 
    
    <xsl:template match="Message">
     <MsgHead>
      <Document>
       <xsl:copy> 
         <xsl:variable name="union">
           <xsl:apply-templates select="* | esl:node-set($DefaultNodes)/DefaultNodes/*[local-name()]"/>
         </xsl:variable>
         <xsl:copy-of select="esl:node-set($union)/*[generate-id() = generate-id(key('kByName',local-name())[1])]" />
       </xsl:copy> 
      </Document> 
     </MsgHead>
    </xsl:template>
    
    <xsl:template match="@*|node()">
      <xsl:copy> 
        <xsl:apply-templates select="@*|node()" />
      </xsl:copy> 
    </xsl:template>    
    
    </xsl:stylesheet>
    

    ...应用于此输入文档时...

    <Message>
      <a>123</a>
      <c>456</c>
      <d>789</d>
    </Message>
    

    ...产生...

    <MsgHead>
      <Document>
        <Message>
          <a>123</a>
          <c>456</c>
          <d>789</d>
          <b>-1</b>
        </Message>
      </Document>
    </MsgHead>
    

    注意: 根据需要将 esl 命名空间更改为 http://exslt.org/common,具体取决于您的 XSLT 处理器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-17
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多