1

この質問の解決策は、サンプルXMLツリーをハードコードされたフラット区切りのテキストファイルに変換します。

string orderXml = 
@"<?xml version='1.0' encoding='utf-8'?>
<Order id='79223510'>
<Status>new</Status>
<ShipMethod>Standard International</ShipMethod>
<ToCity>Tokyo</ToCity>
<Items>
    <Item>
    <SKU>SKU-1234567890</SKU>
    <Quantity>1</Quantity>
    <Price>99.95</Price>
    </Item>
    <Item>
    <SKU>SKU-1234567899</SKU>
    <Quantity>1</Quantity>
    <Price>199.95</Price>
    </Item>
</Items>
</Order>";

StringReader str = new StringReader(orderXml);

var xslt = new XmlTextReader(new StringReader(

    @"<xsl:stylesheet version='1.0' 
    xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

    <xsl:output method='text' indent='no' media-type='text/plain' />
    <xsl:variable name='newline'><xsl:text>&#13;&#10;</xsl:text></xsl:variable>
    <xsl:variable name='delimiter'>|</xsl:variable>

    <!-- by default, don't copy any nodes to output -->
    <xsl:template match='node()|@*'>
        <xsl:apply-templates select='node()|@*'/>
    </xsl:template>

    <xsl:template match='/Order/Items/Item'>
    <xsl:value-of
        select='concat(
        ../../@id, $delimiter,
        ../../Status, $delimiter,
        ../../ShipMethod, $delimiter,
        ../../ToCity, $delimiter,
        SKU, $delimiter,
        Quantity, $delimiter,
        Price,
        $newline)'
        />
    </xsl:template>
    </xsl:stylesheet>"

                ));

var xDoc = new XPathDocument(str);
var xTr = new System.Xml.Xsl.XslCompiledTransform();
xTr.Load(xslt);

StringBuilder sb = new StringBuilder();
StringWriter writer = new StringWriter(sb);
xTr.Transform(xDoc, null, writer);

string[] lines = sb.ToString().Split(new string[] {"\n"}, StringSplitOptions.RemoveEmptyEntries);    

lines.ToList().ForEach(System.Console.Write);     

次のように出力を生成します。

79223510|new|Standard International|Tokyo|SKU-1234567890|1|99.95
79223510|new|Standard International|Tokyo|SKU-1234567899|1|199.95

ソースXMLツリーをトラバースし、親ノードと属性値を子ノードに連結する汎用XSL変換を使用して同じ出力を生成する方法はありますか?

ノート:

  1. ノードの属性がある場合、そのノードには連結する値がありません。

  2. ノードに複数の属性がある場合、それらの値はスラッシュ文字を使用して連結する必要があります。

  3. 真の汎用ソリューションは機能し、3つ以上の階層レベルを持つXMLツリーをフラット化する必要があります。

これは、2つの属性を持つ追加の親ノードを持つ別のサンプルドキュメントです。

<Order id='79223510'>
<Status>new</Status>
<ShipMethod>Standard International</ShipMethod>
<ToCity>Tokyo</ToCity>
<Marketplace id="123-45678-9089808" name="MyBooks" />
<Items>
    <Item>
    <SKU>SKU-1234567890</SKU>
    <Quantity>1</Quantity>
    <Price>99.95</Price>
    </Item>
    <Item>
    <SKU>SKU-1234567899</SKU>
    <Quantity>1</Quantity>
    <Price>199.95</Price>
    </Item>
</Items>
</Order>

そして、これが望ましい区切り文字で区切られたフラット化されたテキストファイルの出力です。

79223510|new|Standard International|Tokyo|123-45678-908980/MyBooks|SKU-1234567890|1|99.95
79223510|new|Standard International|Tokyo|123-45678-908980/MyBooks|SKU-1234567899|1|199.95

Dimitre Novatchevによるソリューションは、元のサンプルドキュメントと、より高いレベルのノード階層を持つXMLドキュメントの両方でうまく機能します。

    string orderXml = 
//      @"<?xml version='1.0' encoding='utf-8'?>
//      <Order id='79223510'>
//      <Status>new</Status>
//      <ShipMethod>Standard International</ShipMethod>
//      <ToCity>Tokyo</ToCity>
//      <Marketplace id='123-45678-9089808' name='MyBooks'/>
//      <Items>
//          <Item>
//          <SKU>SKU-1234567890</SKU>
//          <Quantity>1</Quantity>
//          <Price>99.95</Price>
//          </Item>
//          <Item>
//          <SKU>SKU-1234567899</SKU>
//          <Quantity>1</Quantity>
//          <Price>199.95</Price>
//          </Item>
//      </Items>
//      </Order>";

@"<?xml version='1.0' encoding='utf-8'?>
<Order id='79223510'>
    <Status>new</Status>
    <ShipMethod>Standard International</ShipMethod>
    <ToCity>Tokyo</ToCity>
    <Marketplace id=""123-45678-9089808"" name=""MyBooks"" />
    <Items>
        <Item>
        <X>
            <SKU>SKU-1234567890</SKU>
            <Quantity>1</Quantity>
            <Price>99.95</Price>
        </X>
        <X>
            <SKU>SKU-1234554321</SKU>
            <Quantity>1</Quantity>
            <Price>199.95</Price>
        </X>
        </Item>
        <Item>
        <Y>
            <SKU>SKU-0987654321</SKU>
            <Quantity>1</Quantity>
            <Price>299.95</Price>
        </Y>
        <Y>
            <SKU>SKU-0987667890</SKU>
            <Quantity>1</Quantity>
            <Price>399.95</Price>
        </Y>
        </Item>
    </Items>
</Order>";

StringReader str = new StringReader(orderXml);

var xslt = new XmlTextReader(new StringReader(   
    @"<xsl:stylesheet version='1.0'
        xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
        xmlns:ext='http://exslt.org/common'>
    <xsl:output method='text'/>
    <xsl:strip-space elements='*'/>

        <xsl:param name='pLeafNodes' select=
        '//*[not(*[*])
            and
            (
            name() = name(following-sibling::*[1])
            or
            name() = name(preceding-sibling::*[1])
            )
            ]'/>

        <xsl:template match='/'>
        <xsl:variable name='vrtfPass1'>
            <t>
                <xsl:call-template name='StructRepro'/>
            </t>
        </xsl:variable>

        <xsl:apply-templates mode='pass2'
            select='ext:node-set($vrtfPass1)/*/*' />
        </xsl:template>

    <xsl:template match='Order' mode='pass2'>
    <xsl:apply-templates select='.//@* | .//text()' mode='pass2'/>
    <xsl:text>&#xA;</xsl:text>
    </xsl:template>

    <xsl:template match='@*|text()' mode='pass2'>
    <xsl:if test='not(position()=1) and not(self::text())'>/</xsl:if>
    <xsl:if test='not(position()=1) and self::text()'>|</xsl:if>
    <xsl:value-of select='.'/>
    </xsl:template>

        <xsl:template name='StructRepro'>
        <xsl:param name='pLeaves' select='$pLeafNodes'/>

        <xsl:for-each select='$pLeaves'>
            <xsl:apply-templates mode='build' select='/*'>
            <xsl:with-param name='pChild' select='.'/>
            <xsl:with-param name='pLeaves' select='$pLeaves'/>
            </xsl:apply-templates>
        </xsl:for-each>
        </xsl:template>

        <xsl:template mode='build' match='node()|@*'>
            <xsl:param name='pChild'/>
            <xsl:param name='pLeaves'/>

            <xsl:copy>
            <xsl:apply-templates mode='build' select='@*'/>

            <xsl:variable name='vLeafChild' select=
                '*[count(.|$pChild) = count($pChild)]'/>

            <xsl:choose>
                <xsl:when test='$vLeafChild'>
                <xsl:apply-templates mode='build'
                    select='$vLeafChild
                            |
                            node()[not(count(.|$pLeaves) = count($pLeaves))]'>
                    <xsl:with-param name='pChild' select='$pChild'/>
                    <xsl:with-param name='pLeaves' select='$pLeaves'/>
                </xsl:apply-templates>
                </xsl:when>
                <xsl:otherwise>
                <xsl:apply-templates mode='build' select=
                'node()[not(.//*[count(.|$pLeaves) = count($pLeaves)])
                        or
                        .//*[count(.|$pChild) = count($pChild)]
                        ]
                '>

                    <xsl:with-param name='pChild' select='$pChild'/>
                    <xsl:with-param name='pLeaves' select='$pLeaves'/>
                </xsl:apply-templates>
                        </xsl:otherwise>
                    </xsl:choose>
                    </xsl:copy>
                </xsl:template>
                <xsl:template match='text()'/>
            </xsl:stylesheet>"
                ));


//
// White space cannot be stripped from input documents that have already been loaded. 
// Provide the input document as an XmlReader instead. 
//+
//var xDoc = new XPathDocument(str);
XmlReaderSettings settings;
settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Document;
var xDoc = XmlReader.Create(str, settings);
//-

var xTr = new System.Xml.Xsl.XslCompiledTransform();
xTr.Load(xslt);

StringBuilder sb = new StringBuilder();
StringWriter writer = new StringWriter(sb);
xTr.Transform(xDoc, null, writer);

string[] lines = sb.ToString().Split(new string[] {"\n"}, StringSplitOptions.RemoveEmptyEntries);    

lines.ToList().ForEach(System.Console.Write);     

//  test output 1
//  79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234567890|1|99.95
//  79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234567899|1|199.95

// test output 2
//  79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234567890|1|99.95
//  79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234554321|1|199.95
//  79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-0987654321|1|299.95
//  79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-0987667890|1|399.95 
4

1 に答える 1

0

この変換

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common">
     <xsl:output method="text"/>
     <xsl:strip-space elements="*"/>

     <xsl:param name="pLeafNodes" select=
     "//*[not(*[*])
        and
         (
          name() = name(following-sibling::*[1])
         or
          name() = name(preceding-sibling::*[1])
          )
         ]"/>

     <xsl:template match="/">
      <xsl:variable name="vrtfPass1">
          <t>
            <xsl:call-template name="StructRepro"/>
          </t>
      </xsl:variable>

      <xsl:apply-templates mode="pass2"
         select="ext:node-set($vrtfPass1)/*/*" />
     </xsl:template>

 <xsl:template match="Order" mode="pass2">
  <xsl:apply-templates select=".//@* | .//text()" mode="pass2"/>
  <xsl:text>&#xA;</xsl:text>
 </xsl:template>

 <xsl:template match="@*|text()" mode="pass2">
  <xsl:if test="not(position()=1) and not(self::text())">/</xsl:if>
  <xsl:if test="not(position()=1) and self::text()">|</xsl:if>
  <xsl:value-of select="."/>
 </xsl:template>


     <xsl:template name="StructRepro">
       <xsl:param name="pLeaves" select="$pLeafNodes"/>

       <xsl:for-each select="$pLeaves">
         <xsl:apply-templates mode="build" select="/*">
          <xsl:with-param name="pChild" select="."/>
          <xsl:with-param name="pLeaves" select="$pLeaves"/>
         </xsl:apply-templates>
       </xsl:for-each>
     </xsl:template>

      <xsl:template mode="build" match="node()|@*">
          <xsl:param name="pChild"/>
          <xsl:param name="pLeaves"/>

         <xsl:copy>
           <xsl:apply-templates mode="build" select="@*"/>

           <xsl:variable name="vLeafChild" select=
             "*[count(.|$pChild) = count($pChild)]"/>

           <xsl:choose>
            <xsl:when test="$vLeafChild">
             <xsl:apply-templates mode="build"
                 select="$vLeafChild
                        |
                          node()[not(count(.|$pLeaves) = count($pLeaves))]">
                 <xsl:with-param name="pChild" select="$pChild"/>
                 <xsl:with-param name="pLeaves" select="$pLeaves"/>
             </xsl:apply-templates>
            </xsl:when>
            <xsl:otherwise>
             <xsl:apply-templates mode="build" select=
             "node()[not(.//*[count(.|$pLeaves) = count($pLeaves)])
                    or
                     .//*[count(.|$pChild) = count($pChild)]
                    ]
             ">

                 <xsl:with-param name="pChild" select="$pChild"/>
                 <xsl:with-param name="pLeaves" select="$pLeaves"/>
             </xsl:apply-templates>
            </xsl:otherwise>
           </xsl:choose>
         </xsl:copy>
     </xsl:template>
     <xsl:template match="text()"/>
</xsl:stylesheet>

提供されたXMLドキュメントに適用した場合

<Order id='79223510'>
    <Status>new</Status>
    <ShipMethod>Standard International</ShipMethod>
    <ToCity>Tokyo</ToCity>
    <Marketplace id="123-45678-9089808" name="MyBooks" />
    <Items>
        <Item>
            <SKU>SKU-1234567890</SKU>
            <Quantity>1</Quantity>
            <Price>99.95</Price>
        </Item>
        <Item>
            <SKU>SKU-1234567899</SKU>
            <Quantity>1</Quantity>
            <Price>199.95</Price>
        </Item>
    </Items>
</Order>

必要な正しい結果を生成します。

79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234567890|1|99.95
79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234567899|1|199.95

これは、提供されたものに基づいて作成した、より複雑なXMLドキュメントです

<Order id='79223510'>
    <Status>new</Status>
    <ShipMethod>Standard International</ShipMethod>
    <ToCity>Tokyo</ToCity>
    <Marketplace id="123-45678-9089808" name="MyBooks" />
    <Items>
        <Item>
         <X>
            <SKU>SKU-1234567890</SKU>
            <Quantity>1</Quantity>
            <Price>99.95</Price>
         </X>
         <X>
            <SKU>SKU-1234554321</SKU>
            <Quantity>1</Quantity>
            <Price>199.95</Price>
         </X>
        </Item>
        <Item>
         <Y>
            <SKU>SKU-0987654321</SKU>
            <Quantity>1</Quantity>
            <Price>299.95</Price>
         </Y>
         <Y>
            <SKU>SKU-0987667890</SKU>
            <Quantity>1</Quantity>
            <Price>399.95</Price>
         </Y>
        </Item>
    </Items>
</Order>

同じ変換がこの2番目のドキュメントに適用されると、再び必要な正しい結果が生成されます。

79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234567890|1|99.95
79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-1234554321|1|199.95
79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-0987654321|1|299.95
79223510|new|Standard International|Tokyo/123-45678-9089808/MyBooks|SKU-0987667890|1|399.95

説明

  1. これは2パス変換です。

  2. 最初のパスは、ソースXMLドキュメントを細断されたものに変換します。この回答からの一般的なシュレッダーの解決策が使用されます。ここで最も重要なことは、シュレッダーの「リーフ」ノードを正しく指定することです。これらは、次のような要素ノードです。1)それ自体が子要素を持つ子要素を持たない。2)その名前がその直前の兄弟要素の名前と同じであるか、その直前の兄弟の名前と同じである。

中間結果は次のとおりです。

<t>
   <Order id="79223510">
      <Status>new</Status>
      <ShipMethod>Standard International</ShipMethod>
      <ToCity>Tokyo</ToCity>
      <Marketplace id="123-45678-9089808" name="MyBooks"/>
      <Items>
         <Item>
            <X>
               <SKU>SKU-1234567890</SKU>
               <Quantity>1</Quantity>
               <Price>99.95</Price>
            </X>
         </Item>
      </Items>
   </Order>
   <Order id="79223510">
      <Status>new</Status>
      <ShipMethod>Standard International</ShipMethod>
      <ToCity>Tokyo</ToCity>
      <Marketplace id="123-45678-9089808" name="MyBooks"/>
      <Items>
         <Item>
            <X>
               <SKU>SKU-1234554321</SKU>
               <Quantity>1</Quantity>
               <Price>199.95</Price>
            </X>
         </Item>
      </Items>
   </Order>
   <Order id="79223510">
      <Status>new</Status>
      <ShipMethod>Standard International</ShipMethod>
      <ToCity>Tokyo</ToCity>
      <Marketplace id="123-45678-9089808" name="MyBooks"/>
      <Items>
         <Item>
            <Y>
               <SKU>SKU-0987654321</SKU>
               <Quantity>1</Quantity>
               <Price>299.95</Price>
            </Y>
         </Item>
      </Items>
   </Order>
   <Order id="79223510">
      <Status>new</Status>
      <ShipMethod>Standard International</ShipMethod>
      <ToCity>Tokyo</ToCity>
      <Marketplace id="123-45678-9089808" name="MyBooks"/>
      <Items>
         <Item>
            <Y>
               <SKU>SKU-0987667890</SKU>
               <Quantity>1</Quantity>
               <Price>399.95</Price>
            </Y>
         </Item>
      </Items>
   </Order>
</t>

.3。2番目のパスは、中間結果の最上位要素の子要素を処理します。処理はモード「pass2」で実行されます。

この2番目のパスの処理はかなり単純です。すべての子孫属性または子孫テキストノードはドキュメント順に処理され、それらの値はノードのタイプに対応する区切り文字(テキストノードの場合は「|」、の場合は「/」)で出力されます。属性)。

于 2012-06-30T04:16:10.970 に答える