【问题标题】:Transforming flat XML to a hierarchy (XML or Object) with C# and LINQ使用 C# 和 LINQ 将平面 XML 转换为层次结构(XML 或对象)
【发布时间】:2016-06-25 23:53:29
【问题描述】:

我看到有几篇关于同一主题的文章,但几个因素的结合使这个问题更具挑战性。

我需要转换如下所示的平面 XML:

string myXML = "<a>" +
    "<b><levelNumber>01</levelNumber><name>top1</name></b>" +
    "<b><levelNumber>05</levelNumber><name>lev2a</name></b>" +
    "<b><levelNumber>05</levelNumber><name>lev2b</name></b>" +
    "<b><levelNumber>10</levelNumber><name>lev3a</name></b>" +
    "<b><levelNumber>10</levelNumber><name>lev3b</name></b>" +
    "<b><levelNumber>10</levelNumber><name>lev3c</name></b>" +
    "<b><levelNumber>05</levelNumber><name>lev2c</name></b>" +
    "<b><levelNumber>10</levelNumber><name>lev3d</name></b>" +
    "<b><levelNumber>01</levelNumber><name>top2</name></b>" +
    "<b><levelNumber>05</levelNumber><name>lev2d</name></b>" +
    "<b><levelNumber>05</levelNumber><name>lev2e</name></b>" +
    "<b><levelNumber>05</levelNumber><name>lev2f</name></b>" +
    "</a>";

... 嵌套对象结构或什至只是分层 XML 结构,最好使用 LINQ。确定层次结构的值是 levelNumber 元素。简单地说,那些具有较高数字的元素应该是前面较低数字的子元素。

所需的 XML 应如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<a>
    <b>
        <levelNumber>01</levelNumber>
        <name>top1</name>
    </b>
    <children>
        <b>
            <levelNumber>05</levelNumber>
            <name>lev2a</name>
        </b>
        <b>
            <levelNumber>05</levelNumber>
            <name>lev2b</name>
        </b>
        <children>
            <b>
                <levelNumber>10</levelNumber>
                <name>lev3a</name>
            </b>
            <b>
                <levelNumber>10</levelNumber>
                <name>lev3b</name>
            </b>
            <b>
                <levelNumber>10</levelNumber>
                <name>lev3c</name>
            </b>
        </children>
        <b>
            <levelNumber>05</levelNumber>
            <name>lev2c</name>
        </b>
        <children>
            <b>
                <levelNumber>10</levelNumber>
                <name>lev3d</name>
            </b>
        </children>
    </children>
    <b>
        <levelNumber>01</levelNumber>
        <name>top2</name>
    </b>
    <children>
        <b>
            <levelNumber>05</levelNumber>
            <name>lev2d</name>
        </b>
        <b>
            <levelNumber>05</levelNumber>
            <name>lev2e</name>
        </b>
        <b>
            <levelNumber>05</levelNumber>
            <name>lev2f</name>
        </b>
    </children>
</a>

虽然有一些相关的解决方案,但一直困扰我的部分是需要嵌套相同类型的元素。

我从通过窥视阅读平面结构的方法开始: (使用函数提取level的数值)

int firstSibling = GetLevel(element.ElementsAfterSelf("b").First());
var childElements = element.ElementsAfterSelf("b")
             .TakeWhile(x => GetLevel(x) < firstSibling);  // all elements with higher level number

然后我会通过 childElements 进行递归(比如具有子元素的元素 lev2b,但是从中得出更多的 5 级,我不确定如何使用循环或 LINQ 或两者来捕获这些。

此外,如果您希望处理无限深度,那么您将不得不在处理子元素的函数中递归,并通过相同的过程在每个子元素中搜索子元素。

就像我一开始说的,这整个结构最终会变成一个嵌套的对象结构,但是如果这太难了,我愿意从一个嵌套的 XML 结构开始工作。

欢迎对此提出任何想法。

谢谢。 科尼尔。

【问题讨论】:

    标签: c# xml linq hierarchy flat


    【解决方案1】:

    我们能够提出一个利用 LINQ 并提供所需分层 XML 的解决方案。

    void Main()
    {
        var elements = GetElements(GetMyXml());
        //elements.Dump();
        var workingStorageVariables = GetWorkingStorageVariables(elements);
        //workingStorageVariables.Dump();
    
        var tree = new XElement("a",
            workingStorageVariables
                .Where(h => h.Level == 1)
                .Select
                (
                    h => new XElement("children", 
                            new XElement("b",
                                new XElement("levelNumber", h.LevelText),
                                new XElement("name", h.VariableName),
                                GetChildVariables(workingStorageVariables, h))
                    )
                )
        );
    
        Console.WriteLine(tree);
    }
    
    public static string GetMyXml()
    {
        return "<a>" +
        "<b><levelNumber>01</levelNumber><name>top1</name></b>" +
        "<b><levelNumber>05</levelNumber><name>lev2a</name></b>" +
        "<b><levelNumber>05</levelNumber><name>lev2b</name></b>" +
        "<b><levelNumber>10</levelNumber><name>lev3a</name></b>" +
        "<b><levelNumber>10</levelNumber><name>lev3b</name></b>" +
        "<b><levelNumber>10</levelNumber><name>lev3c</name></b>" +
        "<b><levelNumber>05</levelNumber><name>lev2c</name></b>" +
        "<b><levelNumber>10</levelNumber><name>lev3d</name></b>" +
        "<b><levelNumber>01</levelNumber><name>top2</name></b>" +
        "<b><levelNumber>05</levelNumber><name>lev2d</name></b>" +
        "<b><levelNumber>05</levelNumber><name>lev2e</name></b>" +
        "<b><levelNumber>05</levelNumber><name>lev2f</name></b>" +
        "</a>";
    }
    
    public static IEnumerable<MyElement> GetElements(string xml)
    {
        XDocument doc = XDocument.Parse(xml);
    
        return doc.Root
                  .Elements()
                  .Elements()
                  .Select(x => new MyElement
                  {
                      ElementName = x.Name.LocalName,
                      ElementValue = x.Value
                  });
    }
    
    public static IEnumerable<WorkingStorageVariable> GetWorkingStorageVariables(IEnumerable<MyElement> elements)
    {
        List<WorkingStorageVariable> workingStorageVariables = new List<WorkingStorageVariable>();
    
        int level = 0;
        string levelText = String.Empty;
        string variableName = String.Empty;
    
        foreach (var element in elements)
        {
            if (element.ElementName == "levelNumber")
            {
                levelText = element.ElementValue;
                level = Convert.ToInt32(element.ElementValue);
            }
    
            if (level != 0 && element.ElementName == "name")
            {
                variableName = element.ElementValue;
                workingStorageVariables.Add(new WorkingStorageVariable { Level = level, LevelText = levelText, VariableName = variableName });
                level = 0;
            }
        }
    
        return workingStorageVariables;
    }
    
    public static IEnumerable<XElement> GetChildVariables(
                IEnumerable<WorkingStorageVariable> workingStorageVariables,
                WorkingStorageVariable parent)
    {
        return
            workingStorageVariables
                .SkipWhile(h => h != parent)
                .Skip(1)
                .TakeWhile(h => h.Level > parent.Level)
                .Select(h =>
                    new XElement("children",
                        new XElement("b",
                            new XElement("levelNumber", h.LevelText),
                            new XElement("Name", h.VariableName),
                            GetChildVariables(workingStorageVariables, h)
                        ))
                );
    }
    
    public class MyElement
    {
        public string ElementName { get; set; }
        public string ElementValue { get; set; }
    }
    
    public class WorkingStorageVariable
    {
        public int Level { get; set; }
        public string LevelText { get; set; }
        public string VariableName { get; set; }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多