この質問の解決策は、サンプル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> </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変換を使用して同じ出力を生成する方法はありますか?
ノート:
ノードの属性がある場合、そのノードには連結する値がありません。
ノードに複数の属性がある場合、それらの値はスラッシュ文字を使用して連結する必要があります。
真の汎用ソリューションは機能し、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>
</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