3

我想转换这个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>

进入这个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>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. X 和 Y 节点值必须替换为 /RealValuesRoot/Value 节点的值。
  2. 必须从输出中删除 /RealValuesRoot 节点。
  3. xml 的其余部分在输出中必须保持不变。

“值”节点具有唯一 ID,表示消息正文中的唯一 xpath,例如 ID 1 引用 xpath /Message/Body/Node1/Node2/Node3/NodeX。

我得用微软的xslt 1.0版!!

我已经有一个可以正常工作的 xslt 并且可以满足我的所有需求,但我对性能并不满意!

我的 xslt 工作方式如下:

  1. 我创建了一个全局字符串变量,其作用类似于键值对,例如:1:xpath1_2:xpath2_ … _N:xpathN。此变量将“Value”节点的 ID 与消息正文中需要替换的节点相关联。

  2. xslt 从根节点开始递归地迭代输入 xml。

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

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

如前所述,我的 xslt 工作正常,但我想尽可能提高性能,请随时提出一个全新的 xslt 逻辑!欢迎您的想法和建议!

4

2 回答 2

2

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

这可能是您的效率低下 - 如果您每次可能正在查看 O(N 2 ) 算法时都重新计算返回根的路径。在没有看到您的 XSLT 的情况下,这是相当投机的,但您可以通过使用参数将当前路径向下传递递归来稍微调整一下 - 如果您的主要算法基于标准标识模板

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

然后将其更改为类似

<xsl:template match="@*|node()">
  <xsl:param name="curPath" />
  <xsl:copy>
    <xsl:apply-templates select="@*|node()">
      <xsl:with-param name="curPath" select="concat($curPath, '/', name())" />
    </xsl:apply-templates>
  </xsl:copy>
</xsl:template>

或者无论您的逻辑是什么,都可以构建您需要的路径。现在,在您要按摩的节点的特定模板中,您已经有了到其父节点的路径,并且您不必每次都一直走到根节点。

您可能需要添加一个

<xsl:template match="/">
  <xsl:apply-templates />
</xsl:template>

所以你不会在前面看到双斜杠$curPath


xpath 在 xslt 中被硬编码为全局变量中的字符串

如果您在 XML 结构中表示此映射而不是字符串,那么您可以使用密钥机制来加快查找速度:

<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" />

(添加xmlns:msxsl="urn:schemas-microsoft-com:xslt"到您的xsl:stylesheet)。或者,如果您想避免使用node-set扩展功能,则可以使用替代定义

<xsl:variable name="lookupTable" select="document('')//xsl:variable[name='rtfLookupTable']" />

它通过将样式表本身视为纯 XML 文档来工作。

跨多个文档的键在 XSLT 1.0 中有点繁琐,但可以做到,本质上您必须$lookupTable在调用键函数之前切换当前上下文以指向,因此您需要将当前上下文保存在变量中以供您参考后来:

<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>

或者实际上,为什么不让 XSLT 引擎为您完成繁重的工作。而不是将您的映射表示为字符串

/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>

我相信您可以看到如何使用某种模板机制(甚至可以是另一个 XSLT)从现有映射轻松地自动生成特定模板。

于 2013-10-09T17:00:46.487 回答
1

最有效的方法是使用 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>

这是此解决方案的实时预览:http ://www.xmlplayground.com/R78v0n


这是一个概念验证解决方案,它使用 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>

它转变

<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>

<root>
  <value id="foo">this is the new value</value>
</root>

也许你可以在你的设置中走这条路。

思路是这样的:

  • 迭代您拥有的所有 XPath,并使用.selectSingleNode().
  • 将每个评估结果(理想情况下是一个节点)及其唯一 ID 作为键值对存储在对象中。这使用 XSLTgenerate-id()从节点获取 ID。
  • 现在正常转换输入。对于每个有问题的节点,获取其 ID 并检查该节点是否确实存在“新值”。
  • 如果是,则插入该新值,如果不是,则继续转换。

使用 msxsl.exe 成功测试。

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

于 2013-10-09T16:30:05.840 回答