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

有没有办法使用通用 XSL 转换遍历源 XML 树并将父节点和属性值连接到子节点来产生相同的输出?

笔记:

  1. 如果节点有任何属性,则该节点将没有要连接的值。

  2. 如果一个节点有多个属性,那么它们的值应该使用斜杠字符连接。

  3. 真正的通用解决方案应该能够工作并扁平化具有两个以上层次结构的 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-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>

当在第二个文档上应用相同的转换时,再次产生所需的正确结果

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. 第一遍将源 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. 第二遍处理中间结果顶部元素的子元素——处理在模式“pass2”中完成。

这个第二遍处理相当简单——所有后代属性或后代文本节点都按文档顺序处理,并且它们的值使用与节点类型对应的分隔符输出('|' 用于文本节点,'/' 用于属性)。

于 2012-06-30T04:16:10.970 回答