扁平化 xml 节点的 Linq 方法语法

Posted

技术标签:

【中文标题】扁平化 xml 节点的 Linq 方法语法【英文标题】:Linq method syntax to flatten xml nodes 【发布时间】:2019-10-15 15:38:57 【问题描述】:

我有下面的工作代码来展平 xml 节点:

var xml = XDocument.Parse(input);

var headerSection = xml.Descendants("Header");
var subHeaderSection = xml.Descendants("SubHeader");

var body = xml.Descendants("Body");
var activity = body.Descendants("Activity");
var position = body.Descendants("Position");
var positionAmouts = position.Descendants("PositionAmounts").Elements("PositionAmount");

var results = from h in headerSection
              from sh in subHeaderSection
              from a in activity              
              from po in position
              from pa in positionAmouts
              select new List<string>
              
                  h.Element("Id").Value,
                  h.Element("OtherId").Value,
                  h.Element("SomeValue").Value,
                  sh.Element("SomeValue2").Value,
                  sh.Element("SomeValue3").Value,
                  a.Element("ActivityId").Value,
                  a.Element("ActivityValue").Value,
                  po.Element("PositionId").Value,
                  pa.Element("Amount1").Value,
                  pa.Element("Amount2").Value,
              

此代码运行良好。但实际的 froms 列表比显示的要长。列表发起者变成了一个长长的值列表。

我相信如果我可以将许多 froms 转换为 linq 方法语法,我应该能够在一种方法中生成相同的 List 结果,在该方法中我可以做的事情比列出所有所需的值更好? (就像循环通过一些预定义的元素名称)

编辑:根据要求的示例 xml:

<Root>
  <Header>
    <Id>123</Id>
    <OtherId>456</OtherId>
    <SomeValue>abc</SomeValue>    
  </Header>
  <SubHeader>
    <SomeValue2>dfg</SomeValue2>
    <SomeValue3>fghj</SomeValue3>    
  </SubHeader>
  <Body>
    <Activity>
      <ActivityId>321</ActivityId>
      <ActivityValue>hjk</ActivityValue>      
    </Activity>    
    <Position>
      <PositionId>654</PositionId>      
      <PositionAmounts>
        <PositionAmount>
          <Amount1>10.01</Amount1>
          <Amount2>12.63</Amount2>          
        </PositionAmount>
        <PositionAmount>
          <Amount1>15.11</Amount1>
        </PositionAmount>        
      </PositionAmounts>
    </Position>
  </Body>
</Root>

【问题讨论】:

能否提供原xml? @vladimir,已添加 【参考方案1】:

试试这个方法:

const string splitterElementName = "PositionAmount";

var doc = XDocument.Parse(input);

var result = doc.Descendants()
    // Take just the deepest descendants.
    .Where(v => !v.HasElements)
    .Select(v => v.AncestorsAndSelf())
    // Group by key, where key contains names of all ancestors exclude the deepest element.
    .GroupBy(k =>
        
            var isSplitter = false;

            var key = string.Join("-", k
                .Skip(1)
                .Select(v =>
                
                    if (v.Name.LocalName != splitterElementName)
                    
                        return v.Name.LocalName;
                    

                    isSplitter = true;

                    // Define the unique id for each splitting element.
                    return $"v.Name.LocalName:string.Join(":", v.AncestorsAndSelf().Select(a => a.ElementsBeforeSelf().Count()))";
                ));

            return (key, isSplitter);
        ,
        e => e.First().Value)
    // Merge all groups to one list.
    .GroupBy(k => 0, e => e, (key, element) => element.ToArray())
    // Extract all splitters.
    .SelectMany(groups => groups
        .Where(group => group.Key.isSplitter)
        .Select(splittingGroup => (splittingKey: splittingGroup.Key, groups)))
    // Prepare result.
    .Select(v => v.groups
        .Where(group => !group.Key.isSplitter || group.Key == v.splittingKey)
        .SelectMany(a => a)
        .ToArray())
    .ToList();

    /* result:
    [0]: string[10]
    [1]: string[9]
result[0]
string[10]
    [0]: "123"
    [1]: "456"
    [2]: "abc"
    [3]: "dfg"
    [4]: "fghj"
    [5]: "321"
    [6]: "hjk"
    [7]: "654"
    [8]: "10.01"
    [9]: "12.63"
result[1]
string[9]
    [0]: "123"
    [1]: "456"
    [2]: "abc"
    [3]: "dfg"
    [4]: "fghj"
    [5]: "321"
    [6]: "hjk"
    [7]: "654"
    [8]: "15.11"
     */

测试xml:

var input = @"<Root>
  <Header>
    <Id>123</Id>
    <OtherId>456</OtherId>
    <SomeValue>abc</SomeValue>    
  </Header>
  <SubHeader>
    <SomeValue2>dfg</SomeValue2>
    <SomeValue3>fghj</SomeValue3>    
  </SubHeader>
  <Body>
    <Activity>
      <ActivityId>321</ActivityId>
      <ActivityValue>hjk</ActivityValue>      
    </Activity>    
    <Position>
      <PositionId>654</PositionId>      
      <PositionAmounts>
        <PositionAmount>
          <Amount1>10.01</Amount1>
          <Amount2>12.63</Amount2>          
        </PositionAmount>
        <PositionAmount>
          <Amount1>15.11</Amount1>
        </PositionAmount>        
      </PositionAmounts>
    </Position>
  </Body>
</Root>";

【讨论】:

问题中的原始代码生成了一个 List>,所有部分都被折叠 答案已修复,目前的方式看起来有点难看;)

以上是关于扁平化 xml 节点的 Linq 方法语法的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 430 扁平化多级双向链表[递归 栈] HERODING的LeetCode之路

LeetCode - 430 - 扁平化多级双向链表 - Java - 细喔

amazon mws 订单有助于简化、扁平化 xml

转换分层 DOM 树 XML 文件:XSLT 能否转换为“扁平化”XML 文件而不会丢失数据?

C#扁平化json结构

数据结构与算法之深入解析“扁平化多级双向链表”的求解思路与算法示例