繁体   English   中英

使用C#和LINQ将平面XML转换为层次结构(XML或对象)

[英]Transforming flat XML to a hierarchy (XML or Object) with C# and LINQ

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

我需要转换如下所示的平面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>

尽管有一些相关的解决方案,但让我感到困扰的部分是需要嵌套相同类型的元素。

我先从偷看平面结构的方法开始:(使用函数提取级别的数值)

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结构。

任何对此的想法都将受到欢迎。

谢谢。 Corneel。

我们能够提出一种使用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; }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM