0

ここにはxml/xsltマージ関連の質問がほとんどないことは知っていますが、私が抱えている問題を解決するものはないようです.

私が探しているのは XSLT (可能な限り汎用 - 入力 XML ファイルの構造に厳密ではない) です。

a.xml を b.xml とマージし、次のような方法で c.xml を生成します。

  • c.xml には、a.xml と b.xml の間の共通ノードが含まれます (ノード値は a.xml から取得されます)。
  • さらに、c.xml には、a.xml ではなく b.xml に存在するノード (および値) が含まれます。

例: a.xml のマージ:

<root_node>
  <settings>
    <setting1>a1</setting1>
    <setting2>a2</setting2>
    <setting3>
      <setting31>a3</setting31>
    </setting3>
    <setting4>a4</setting4>
  </settings>
</root_node>

b.xmlを使用:

<root_node>
  <settings>
    <setting1>b1</setting1>
    <setting2>b2</setting2>
    <setting3>
      <setting31>b3</setting31>
    </setting3>
    <setting5 id="77">b5</setting5>
  </settings>
</root_node>

c.xmlを生成します:

<root_node>
  <settings>
  <setting1>a1</setting1>
  <setting2>a2</setting2>
  <setting3>
    <setting31>a3</setting31>
  </setting3>
  <setting5 id="77">b5</setting5>
</settings>

追加情報

私が理解していることを「共通ノード」で説明しようとします。私は専門家ではないので、これは正確な xml/xslt 定義ではないかもしれません。

a /root_node/settings/ setting1は、 b /root_node/settings/ setting1を持つ「共通ノード」です。これは、2 つのノードが同じパスを使用して到達するためです。設定 2 と設定 3 についても同様です。

2 つの「非共通ノード」は a.xmlでのみ検出される/root_node/settings/ setting4 (出力には含まれない) と、b.xml でのみ検出されるb /root_node/settings/ setting5 (それは出力に入るはずです)。

「一般的なソリューション」とは、入力 XML がどのような形式であっても機能するという意味ではありません。つまり、「これは a.xml 内のノードが一意である場合にのみ機能する」などの制限を追加したり、適切だと思われるその他の制限を追加したりしながら、xslt にハードコードの xpath を含めてはならないということです。

4

2 に答える 2

2

次の XSLT 1.0 プログラムは、必要なことを行います。

に適用しb.xml、パスをa.xmlパラメーターとして渡します。

これがどのように機能するかです。

  1. との間の共通要素Bだけでなく、保持したい新しいノードが含まれているため、 をトラバースします。 AB
    1. 私は「共通要素」を、同じ単純なパスを持つ任意の要素と定義しています。
    2. 「単純なパス」を、スラッシュで区切られた祖先要素の名前のリストと要素自体、つまりancestor-or-self軸として定義します。
      したがって、サンプルB<setting31>は、​​ の単純なパスになりroot_node/settings/setting3/setting31/ます。
    3. このパスはあいまいです。つまり、入力で同じ親を共有する同じ名前の 2 つの要素を使用することはできません。あなたのサンプルに基づいて、そうではないと思います。
  2. すべてのリーフ テキスト ノード(それ以上子要素を持たない要素内の任意のテキスト ノード)
    1. 単純なパスは、 というテンプレートを使用して計算されますcalculatePath
    2. 他のドキュメントから対応する単純なパスnodeValueByPathのテキスト値を取得しようとする再帰テンプレートが呼び出されます。
    3. 対応するテキスト ノードが見つかった場合は、その値が使用されます。これは最初の箇条書きを満たしています。
    4. 対応するノードが見つからない場合、手元にある値、つまり からの値が使用されBます。これは、2 番目の箇条書きを満たしています。

その結果、新しいドキュメントはBの構造と一致し、以下が含まれます。

  • Bに対応するノードがないからのすべてのテキスト ノード値A
  • A対応するノードが存在するときのテキスト ノード値B

XSLT は次のとおりです。

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

  <xsl:param name="aXmlPath" select="''" />
  <xsl:param name="aDoc"     select="document($aXmlPath)" />

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

  <!-- text nodes will be checked against doc A -->
  <xsl:template match="*[not(*)]/text()">
    <xsl:variable name="path">
      <xsl:call-template name="calculatePath" />
    </xsl:variable>

    <xsl:variable name="valueFromA">
      <xsl:call-template name="nodeValueByPath">
        <xsl:with-param name="path"    select="$path" />
        <xsl:with-param name="context" select="$aDoc" />
      </xsl:call-template>
    </xsl:variable>

    <xsl:choose>
      <!-- either there is something at that path in doc A -->
      <xsl:when test="starts-with($valueFromA, 'found:')">
        <!-- remove prefix added in nodeValueByPath, see there --> 
        <xsl:value-of select="substring-after($valueFromA, 'found:')" />
      </xsl:when>
      <!-- or we take the value from doc B -->
      <xsl:otherwise>
        <xsl:value-of select="." />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- this calcluates a simpe path for a node -->
  <xsl:template name="calculatePath">
    <xsl:for-each select="..">
      <xsl:call-template name="calculatePath" />
    </xsl:for-each>
    <xsl:if test="self::*">
      <xsl:value-of select="concat(name(), '/')" />
    </xsl:if>
  </xsl:template>

  <!-- this retrieves a node value by its simple path -->
  <xsl:template name="nodeValueByPath">
    <xsl:param name="path"    select="''" />
    <xsl:param name="context" select="''" />

    <xsl:if test="contains($path, '/') and count($context)">
      <xsl:variable name="elemName" select="substring-before($path, '/')" />
      <xsl:variable name="nextPath" select="substring-after($path, '/')" />
      <xsl:variable name="currContext" select="$context/*[name() = $elemName][1]" />

      <xsl:if test="$currContext">
        <xsl:choose>
          <xsl:when test="contains($nextPath, '/')">
            <xsl:call-template name="nodeValueByPath">
              <xsl:with-param name="path"    select="$nextPath" />
              <xsl:with-param name="context" select="$currContext" />
            </xsl:call-template>
          </xsl:when>
          <xsl:when test="not($currContext/*)">
            <!-- always add a prefix so we can detect 
                 the case "exists in A, but is empty" -->
            <xsl:value-of select="concat('found:', $currContext/text())" />
          </xsl:when>
        </xsl:choose>
      </xsl:if>
    </xsl:if>    
  </xsl:template>
</xsl:stylesheet>
于 2012-07-26T14:10:34.883 に答える
2

複数のファイルを操作する基本的な手法は、document() 関数を使用することです。ドキュメント関数は次のようになります。

<xsl:variable name="var1" select="document('http://example.com/file1.xml', /)"/>
<xsl:variable name="var2" select="document('http://example.com/file2.xml', /)"/>

2 つのドキュメントを取得したら、それらのコンテンツを同じドキュメントで使用できるように使用できます。

于 2012-07-26T13:02:21.500 に答える