2

第一次在这里发帖,找不到我需要的东西。我有 2 个 XML 文档,它们具有相似但略有不同的结构,并且标签中的值可能不同。我需要遍历一个 XML 中的所有叶节点,如果相同的标记(仅通过完整的 XPath 位置)位于另一个 XML 中,我需要将该值复制到目标 XML。我需要自动执行此操作。不使用属性。

例如,我的基地:

<root>
 <a>abc</a>
 <b>def</b>
</root>

目标:

<root>
 <a>xyz</a>
 <c>ghi</c>
</root>

预期输出:

<root>
 <a>abc</a>
 <c>ghi</c>
</root>

使用 XSLT 和简单的 Linux 工具/shell 脚本将比第三方工具更可取。谢谢!

4

2 回答 2

0

这是一个 XSLT 1.0 解决方案——我们只假设两个文档没有混合内容元素(没有元素同时具有元素和非空白文本节点子节点):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <my:patternDoc>
        <root>
         <a>abc</a>
         <b>def</b>
        </root>
     </my:patternDoc>

 <xsl:variable name="vinitTarget" select="/"/>
 <xsl:variable name="vinitPattern" select="document('')/*/my:patternDoc"/>

 <xsl:template match="/*">
  <xsl:copy>
   <xsl:call-template name="clone"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template name="clone">
  <xsl:param name="pTargetNode" select="/*/*[1]"/>
  <xsl:param name="pPatterntNode" select="$vinitPattern/*/*[1]"/>

  <xsl:if test="$pTargetNode">
  <xsl:choose>
    <xsl:when test="not(name($pTargetNode) = name($pPatterntNode))">
      <xsl:copy-of select="$pTargetNode"/>

      <xsl:call-template name="clone">
        <xsl:with-param name="pTargetNode"
          select="$pTargetNode/following-sibling::*[1]"/>
        <xsl:with-param name="pPatterntNode"
          select="$pPatterntNode/following-sibling::*[1]"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
     <xsl:choose>
       <xsl:when test="$pTargetNode[not(*)] and $pPatterntNode[not(*)]">
         <xsl:copy-of select="$pPatterntNode"/>
             <xsl:call-template name="clone">
                <xsl:with-param name="pTargetNode"
                  select="$pTargetNode/following-sibling::*[1]"/>
                <xsl:with-param name="pPatterntNode"
                  select="$pPatterntNode/following-sibling::*[1]"/>
             </xsl:call-template>
       </xsl:when>
       <xsl:when test="$pTargetNode/*">
             <xsl:element name="{name($pTargetNode)}"
                  namespace="{namespace-uri($pTargetNode)}">
                 <xsl:call-template name="clone">
                    <xsl:with-param name="pTargetNode"
                      select="$pTargetNode/*[1]"/>
                    <xsl:with-param name="pPatterntNode"
                      select="$pPatterntNode/*[1]"/>
                 </xsl:call-template>
             </xsl:element>
           <xsl:call-template name="clone">
            <xsl:with-param name="pTargetNode"
              select="$pTargetNode/following-sibling::*[1]"/>
            <xsl:with-param name="pPatterntNode"
              select="$pPatterntNode/following-sibling::*[1]"/>
           </xsl:call-template>
       </xsl:when>
     </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的“目标”XML 文档时:

<root>
 <a>xyz</a>
 <c>ghi</c>
</root>

产生了想要的正确结果:

<root>
   <a xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my">abc</a>
   <c>ghi</c>
</root>

注意

复制的额外名称空间只是因为模式文档当前嵌入到 XSLT 样式表中。这只是为了方便。在实际情况下,模式文档将与 XSLT 样式表分开,并且不会复制额外的名称空间节点。

让我们验证提供的转换是否可以在更复杂的 XML 文档上正常工作:

<root>
    <a>
        <b>
            <c>xyz</c>
            <f/>
            <g>tuv</g>
        </b>
    </a>
    <d>
        <e>ghi</e>
    </d>
</root>

如果同一转换(上图)中的嵌入模式文档现在是:

<my:patternDoc>
    <root>
        <a>
            <b>
             <c>abc</c>
         <f/>
         <g>mnk</g>
         </b>
        </a>
        <d>
            <e>pqr</e>
        </d>
     </root>
  </my:patternDoc>

再次产生所需的正确结果:

<root>
   <a>
      <b>
         <c xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my">abc</c>
         <f xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my"/>
         <g xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my">mnk</g>
      </b>
   </a>
   <d>
      <e xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my">pqr</e>
   </d>
</root>
于 2013-04-05T04:09:35.330 回答
0

XSLT 3.0样式表为没有任何子元素的元素生成动态 XPath 字符串,并用于<xsl:evaluate>从“基本”XML 文件(名为“base.xml”并与 XML 输入位于同一目录中)选择匹配元素这个例子)。

如果“base”中有匹配元素,则使用该元素的文本,否则将复制匹配元素的文本。

<?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:my="my"
    exclude-result-prefixes="xs"
    version="3.0">
    <xsl:output indent="yes"/>

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

<!--Elements that do not have any child elements -->
    <xsl:template match="*[not(*)]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
<!--select the equivalent element from the "base" XML file (if any), 
    using the XPath generated from the my:baseMatchXPath() function --> 
            <xsl:variable name="baseMatch" as="item()*">
                <xsl:evaluate xpath="my:baseMatchXPath(.)"  />    
            </xsl:variable>
<!--
Create a sequence of nodes using the $baseMatch and the currently matched element. 
Use a predicate filter to select the first one 
  (if there is no baseMatch, then the context node is the first).
Apply templates to that element's child node()'s. 

NOTE: In this example it is the text() node, 
      but this would also preserve any comments or processing instructions
-->
            <xsl:apply-templates select="($baseMatch, .)[1]/node()"/>   
        </xsl:copy>
    </xsl:template>

    <xsl:function name="my:baseMatchXPath" as="xs:string">
        <xsl:param name="contextNode"/>
        <xsl:variable name="base">document('base.xml')</xsl:variable>
        <xsl:sequence select="concat($base, 
                                     string-join(my:ancestors($contextNode), ''))"/>
    </xsl:function>

    <xsl:function name="my:ancestors" as="item()*">
      <xsl:param name="contextNode"/>

      <xsl:if test="$contextNode/ancestor::*[1]">
          <xsl:sequence select="my:ancestors($contextNode/ancestor::*[1])"/>
      </xsl:if>
      <xsl:sequence select="'/'"/>
      <xsl:sequence select="name($contextNode)"/>
      <xsl:if test="count($contextNode/parent::*/*[name()=name($contextNode)]) 
                      > 1">
          <xsl:sequence 
               select="concat(
                         '[', 
                         count($contextNode/preceding-sibling::*[name()=name($contextNode)])+1, 
                         ']')"/>
      </xsl:if>            
    </xsl:function>

</xsl:stylesheet>
于 2013-04-05T02:51:33.873 回答