如何使用Microsoft xslt 1.0有效地稍微修改大型xml文档

如何使用Microsoft xslt 1.0有效地稍微修改大型xml文档,xml,xslt-1.0,Xml,Xslt 1.0,我想转换此xml: <Root> <Result> <Message> <Header> <!-- Hundreds of child nodes --> </Header> <Body> <Node1> <!-- Hundreds of child nodes -->

我想转换此xml:

<Root>
  <Result>
    <Message>
      <Header>
        <!-- Hundreds of child nodes -->        
      </Header>
      <Body>
        <Node1>
          <!-- Hundreds of child nodes -->
          <Node2>
            <!-- Hundreds of child nodes -->
            <Node3>
              <!-- Hundreds of child nodes -->
              <NodeX>value 1 to be changed</NodeX>
              <!-- Hundreds of child nodes -->
              <Node4>
                <!-- Hundreds of child nodes -->
                <NodeY>value 2 to be changed</NodeY>
              </Node4>
            </Node3>
          </Node2>          
        </Node1>        
      </Body>
      <RealValuesRoot>
        <!-- These two nodes -->
        <Value ID="1">this value must replace the value of Node X</Value>
        <Value ID="2">this value must replace the value of Node Y</Value>
      </RealValuesRoot>
    </Message>
  </Result>
  <!-- Hundreds to thousands of similar MessageRoot nodes -->
</Root>
<Root>
  <Result>
    <Message>
      <Header>
        <!-- Hundreds of child nodes -->
      </Header>
      <Body>
        <Node1>
          <!-- Hundreds of child nodes -->
          <Node2>
            <!-- Hundreds of child nodes -->
            <Node3>
              <!-- Hundreds of child nodes -->
              <NodeX>this value must replace the value of Node X</NodeX>
              <!-- Hundreds of child nodes -->
              <Node4>
                <!-- Hundreds of child nodes -->
                <NodeY>this value must replace the value of Node Y</NodeY>
              </Node4>
            </Node3>
          </Node2>
        </Node1>
      </Body>
    </Message>
  </Result>
  <!-- Hundreds to thousands of similar MessageRoot nodes -->
</Root>

要更改的值1
要更改的值2
此值必须替换节点X的值
此值必须替换节点Y的值
在此xml中:

<Root>
  <Result>
    <Message>
      <Header>
        <!-- Hundreds of child nodes -->        
      </Header>
      <Body>
        <Node1>
          <!-- Hundreds of child nodes -->
          <Node2>
            <!-- Hundreds of child nodes -->
            <Node3>
              <!-- Hundreds of child nodes -->
              <NodeX>value 1 to be changed</NodeX>
              <!-- Hundreds of child nodes -->
              <Node4>
                <!-- Hundreds of child nodes -->
                <NodeY>value 2 to be changed</NodeY>
              </Node4>
            </Node3>
          </Node2>          
        </Node1>        
      </Body>
      <RealValuesRoot>
        <!-- These two nodes -->
        <Value ID="1">this value must replace the value of Node X</Value>
        <Value ID="2">this value must replace the value of Node Y</Value>
      </RealValuesRoot>
    </Message>
  </Result>
  <!-- Hundreds to thousands of similar MessageRoot nodes -->
</Root>
<Root>
  <Result>
    <Message>
      <Header>
        <!-- Hundreds of child nodes -->
      </Header>
      <Body>
        <Node1>
          <!-- Hundreds of child nodes -->
          <Node2>
            <!-- Hundreds of child nodes -->
            <Node3>
              <!-- Hundreds of child nodes -->
              <NodeX>this value must replace the value of Node X</NodeX>
              <!-- Hundreds of child nodes -->
              <Node4>
                <!-- Hundreds of child nodes -->
                <NodeY>this value must replace the value of Node Y</NodeY>
              </Node4>
            </Node3>
          </Node2>
        </Node1>
      </Body>
    </Message>
  </Result>
  <!-- Hundreds to thousands of similar MessageRoot nodes -->
</Root>

此值必须替换节点X的值
此值必须替换节点Y的值
除了以下变化外,输出与输入几乎相同:

  • X和Y节点值必须替换为/RealValuesRoot/Value节点的值
  • 必须从输出中删除/RealValuesRoot节点
  • xml的其余部分在输出中必须保持不变
  • “值”节点具有表示消息体中唯一xpath的唯一ID,例如ID 1引用为xpath/message/body/Node1/Node2/Node3/NodeX

    我必须使用微软的xslt版本1.0

    我已经有了一个xslt,它可以很好地工作并完成我想要的一切,但是我对它的性能并不满意

    我的xslt的工作原理如下:

  • 我创建了一个全局字符串变量,其作用类似于键值对,类似于:1:xpath1\u 2:xpath2\u…\N:xpathN。此变量将“值”节点的ID与消息正文中需要替换的节点相关联

  • xslt从根节点开始递归地迭代输入xml

  • 我计算当前节点的xpath,然后执行以下操作之一:

  • 如果当前xpath完全匹配全局列表中的一个xpath,那么我将用相应的“value”节点的值替换它的值,并继续迭代
  • 如果当前xpath引用“RealValuesRoot”节点,那么我将省略该节点(不要将其复制到输出中),并继续递归迭代
  • 如果全局ID xpath字符串中不存在当前xpath,则将完整节点复制到输出并继续迭代。(这种情况发生在/Message/Header节点中,该节点永远不会包含任何需要更换的节点)
  • 如果当前xpath部分匹配全局列表中的一个xpath,那么我只需继续递归迭代,直到达到上面3种情况之一

  • 如前所述,我的xslt工作正常,但我希望尽可能提高性能,请随意推荐一个全新的xslt逻辑!欢迎您的意见和建议

    最有效的方法是使用XSL键

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
      <!-- a key that indexes real values by their IDs -->
      <xsl:key name="kRealVal" match="RealValuesRoot/Value" use="@ID" />
    
      <!-- the identity template to copy everything -->    
      <xsl:template match="node() | @*">
        <xsl:copy>
          <xsl:apply-templates select="node() | @*" />
        </xsl:copy>
      </xsl:template>
    
      <!-- ...except elements named <NodeX> -->
      <xsl:template match="*[starts-with(name(), 'Node')]">
        <xsl:variable name="myID" select="substring-after(name(), 'Node')" />
        <xsl:variable name="myRealVal" select="key('kRealVal', $myID)" />
    
        <xsl:copy>
          <xsl:copy-of select="@*" />
          <xsl:choose>
            <xsl:when test="$myRealVal">
              <xsl:value-of select="$myRealVal" />
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates select="node()" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:copy>
      </xsl:template>
    
      <!-- the <RealValuesRoot> element can be trashed -->
      <xsl:template match="RealValuesRoot" />
    </xsl:stylesheet>
    
    
    
    以下是此解决方案的实时预览:


    下面是一个概念验证解决方案,它使用Microsoft脚本扩展来完成繁重的工作:

    <xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:msxsl="urn:schemas-microsoft-com:xslt"
      xmlns:script="http://tempuri.org/script"
    >  
      <msxsl:script language="JScript" implements-prefix="script">
        var index = {};
    
        function getNode(context, xpath) {
          var theContext = context[0],
              theXpath = xpath[0].text,
              result;
    
          try {
            result = theContext.selectSingleNode(theXpath)
          } catch (ex) {
            // xpath is invalid. we could also just throw here
            // but lets return the empty node set.
            result = theContext.selectSingleNode("*[false()]");
          }
          return result;
        }
        function buildIndex(id, node) {
          var theNode = node[0];
    
          if (id) index[id] = theNode;
          return "";
        }
        function getValue(id) {
          return (id in index) ? index[id] : '';
        }
      </msxsl:script>
    
    
      <!-- this is the boilerplate to evaluate all the XPaths -->
      <xsl:variable name="temp">
        <xsl:for-each select="/root/source/map">
          <xsl:value-of select="script:buildIndex(generate-id(script:getNode(/, @xpath)), .)" />
        </xsl:for-each>
      </xsl:variable>
    
      <!-- the identity template to get things rolling -->
      <xsl:template match="node() | @*">
        <xsl:copy>
          <xsl:apply-templates select="node() | @*" />
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="/">
        <!-- actually evaluate $temp once, so the variable is being calculated -->
        <xsl:value-of select="$temp" />
        <xsl:apply-templates select="node() | @*" />
      </xsl:template> 
    
      <!-- all <value> nodes do either have a related "actual value" or they are copied as they are -->
      <xsl:template match="value">
        <xsl:copy>
          <xsl:copy-of select="@*" />
    
          <xsl:variable name="newValue" select="script:getValue(generate-id())" />
          <xsl:choose>
            <xsl:when test="$newValue">
              <xsl:value-of select="$newValue" />
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates select="node() | @*" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:copy>
      </xsl:template>
    
      <!-- the <source> element can be dropped -->
      <xsl:template match="source" />
    
    </xsl:stylesheet>
    
    
    var指数={};
    函数getNode(上下文,xpath){
    var theContext=context[0],
    xpath=xpath[0]。文本,
    结果;
    试一试{
    结果=上下文。选择SingleNode(XPath)
    }捕获(ex){
    //xpath无效。我们也可以在这里抛出
    //但是让我们返回空节点集。
    结果=上下文。选择SingleNode(“*[false()]”);
    }
    返回结果;
    }
    函数buildIndex(id,节点){
    var theNode=node[0];
    如果(id)索引[id]=节点;
    返回“”;
    }
    函数getValue(id){
    返回(索引中的id)?索引[id]:“”;
    }
    
    它转变

    <root>
      <value id="foo">this is to be replaced</value>
    
      <source>
        <map xpath="/root/value[@id = 'foo']">this is the new value</map>
      </source>
    </root>
    
    
    这是要更换的
    这是新的值
    

    
    这是新的值
    
    也许你可以在你的设置中走这条路线

    思路是这样的:

    • 迭代所有XPath,使用
      对其进行评估。选择SingleNode()
    • 将每个评估结果(理想情况下为一个节点)及其唯一ID作为键值对存储在对象中。这使用XSLT的
      generate-id()
      从节点获取id
    • 现在正常转换输入。对于每个有问题的节点,获取其ID并检查该节点是否实际存在“新值”
    • 如果有,则插入新值,如果没有,则继续转换
    已使用msxsl.exe成功测试

    当然,这假设输入中有那些
    元素,但这部分并不是真正必要的,也不容易适应您的实际情况。例如,可以使用JavaScript中的
    split()
    从一长串XPath构建
    index
    对象

    我计算当前节点的xpath,然后执行以下操作之一

    这很可能是您的效率低下-如果您每次都在查看O(N2)算法时重新计算返回根的路径。在没有看到XSLT的情况下,这是一种推测,但是如果您的主要算法基于标准标识模板,那么您可以通过使用参数沿着递归传递当前路径来稍微调整它

    <xsl:template match="@*|node()">
      <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
    </xsl:template>
    
    (将
    xmlns:msxsl=“urn:schemas-microsoft-com:xslt”
    添加到
    xsl:stylesheet
    )。或者如果要避免使用
    节点集
    e
    <xsl:template match="/">
      <xsl:apply-templates />
    </xsl:template>
    
    <xsl:variable name="rtfLookupTable">
      <lookuptable>
        <val xpath="/first/xpath/expression" id="1" />
        <val xpath="/second/xpath/expression" id="2" />
        <!-- ... -->
      </lookuptable>
    </xsl:variable>
    
    <xsl:variable name="lookupTable" select="msxsl:node-set($rtfLookupTable)" />
    
    <xsl:key name="valByXpath" match="val" use="@xpath" />
    
    <xsl:variable name="lookupTable" select="document('')//xsl:variable[name='rtfLookupTable']" />
    
    <xsl:template match="text()">
      <xsl:param name="curPath" />
    
      <xsl:variable name="dot" select="." />
      <xsl:variable name="slash" select="/" />
    
      <xsl:for-each select="$lookupTable">
        <xsl:variable name="valId" select="key('valByXpath', $curPath)/@id" />
        <xsl:choose>
          <xsl:when test="$valId">
            <xsl:value-of select="$slash//Value[@id = $valId]" />
            <!-- or however you extract the right Value -->
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$dot" />
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </xsl:template>
    
    /path/to/node1_1:/path/to/node2_2
    
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <!-- copy everything as-is apart from exceptions below -->
      <xsl:template match="@*|node()">
        <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
      </xsl:template>
    
      <!-- delete the RealValuesRoot -->
      <xsl:template match="RealValuesRoot" />
    
      <xsl:template match="/path/to/node1">
        <xsl:copy><xsl:value-of select="//Value[id='1']" /></xsl:copy>
      </xsl:template>
      <xsl:template match="/path/to/node2">
        <xsl:copy><xsl:value-of select="//Value[id='2']" /></xsl:copy>
      </xsl:template>
    </xsl:stylesheet>