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 の残りの部分は、出力で同じままにする必要があります。

「値」ノードには、メッセージの本文で一意の xpath を表す一意の ID があります。たとえば、ID 1 は xpath /Message/Body/Node1/Node2/Node3/NodeX を参照します。

Microsoft の xslt バージョン 1.0 を使用する必要があります!!

私はすでに正常に動作し、必要なすべてを実行する xslt を持っていますが、パフォーマンスには満足していません!

私のxsltは次のように機能します:

  1. 1:xpath1_2:xpath2_ … _N:xpathN のような、キーと値のペアのように機能するグローバル文字列変数を作成しました。この変数は、「値」ノードの ID を、置き換える必要があるメッセージ本文のノードに関連付けます。

  2. xslt は、入力 xml をルート ノードから再帰的に反復します。

  3. 現在のノードの xpath を計算し、次のいずれかを実行します。

    1. 現在の xpath がグローバル リスト内の xpath の 1 つと完全に一致する場合、その値を対応する「値」ノードの値に置き換えて、反復を続けます。
    2. 現在の xpath が "RealValuesRoot" ノードを参照している場合は、そのノードを省略し (出力にコピーしないでください)、再帰的に反復を続けます。
    3. 現在の xpath がグローバル ID-xpath 文字列に存在しない場合は、完全なノードを出力にコピーし、反復を続行します。(これは、たとえば、交換が必要なノードを決して含まない /Message/Header ノードで発生します)
    4. 現在の xpath がグローバル リスト内の xpath の 1 つと部分的に一致する場合は、上記の 3 つのケースのいずれかに到達するまで単純に再帰的に繰り返します。

前述のとおり、私の xslt は正常に動作しますが、パフォーマンスを可能な限り改善したいと考えています。まったく新しい xslt ロジックを提案してください。あなたのアイデアや提案を歓迎します!

4

2 に答える 2

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()
  • 各評価結果 (理想的には 1 つのノード) をその一意の ID と共に、キーと値のペアとしてオブジェクトに格納します。これは XSLTgenerate-id()を使用してノードから ID を取得します。
  • 入力を通常どおりに変換します。問題の各ノードについて、その ID を取得し、そのノードに「新しい値」が実際に存在するかどうかを確認します。
  • 存在する場合はその新しい値を挿入し、存在しない場合は変換を続けます。

msxsl.exe で正常にテストされました。

もちろん、これは入力にそれらの<map xpath="...">要素があることを前提としていますが、その部分は実際には必要なく、実際の状況に簡単に適応できます。たとえば、JavaScript でindex作成した XPath の長い文字列からオブジェクトを構築できます。split()

于 2013-10-09T16:30:05.840 に答える