简体   繁体   English

XML树生成

[英]XML tree generation

I've got a "flat" XML menu that I need to structure. 我有一个需要构建的“平面”XML菜单。

Current XML tree: 当前的XML树:

<root>
    <nodes>
        <node>
            <id>5</id>
            <parent>1</parent>
        </node>
        <node>
            <id>8</id>
            <parent>5</parent>
        </node>
        <node>
            <id>14</id>
            <parent>8</parent>
        </node>
        <node>
            <id>26</id>
            <parent>1</parent>
        </node>
    </nodes>    
</root>

This XML tree need to be reodered to have correct relations between ID:s and ParentID:S 需要重新编码此XML树以在ID:s和ParentID:S之间建立正确的关系

<root>
    <nodes>
        <node>
            <id>5</id>
            <parent>1</parent>
            <node>
                <id>8</id>
                <parent>5</parent>
                <node>
                    <id>14</id>
                    <parent>8</parent>
                </node>
            </node>
        </node>            
        <node>
            <id>26</id>
            <parent>1</parent>
        </node>
    </nodes>    
</root>

Iv got the following code to try to accomplish this: 我有以下代码来尝试完成此任务:

public XmlDocument SortXmlNodeTree(XmlDocument udoc)
{
    XmlDocument sortedDoc = new XmlDocument();
    sortedDoc.LoadXml(xmlStartString);

    //select top nodes
    //top node -> find all siblings. For each sibling add sibling.siblings. ->loop            
    XmlNode nodes = udoc.DocumentElement.LastChild;
    foreach(XmlNode n in nodes)
    {
        //get top nodes and check if they are folders
        if (n["parent"].InnerText.Equals("1") && n["type"].InnerText.Equals("2"))
        {
            XmlNode newNode = sortedDoc.ImportNode(n, true);                        
            GetNodeSiblings(ref nodes, newNode, ref sortedDoc);       
            SortedDoc.DocumentElement.FirstChild.AppendChild(newNode);                    
        }
    }
    return sortedDoc;
}

public XmlNode GetNodeSiblings(ref XmlNode nodes, XmlNode currentNode, ref XmlDocument tree)
{
    if (!nodes.HasChildNodes)
    {
        return null;
    }

    foreach (XmlNode n in nodes)
    {
        // if we have a folder and parent is currentNode, go deeper
        if (n["type"].InnerText.Equals("2") && n["parent"].InnerText.Equals(currentNode["id"].InnerText))
        {
            XmlNode newNode = tree.ImportNode(n, true);                    
            GetNodeSiblings(ref nodes, newNode, ref tree);
            currentNode.AppendChild(newNode);
        }
        // if we have a product that has currentNode as parent, add it.
        else if (!n["type"].InnerText.Equals("2") && n["parent"].InnerText.Equals(currentNode["id"].InnerText))
        {
            XmlNode newNode = tree.ImportNode(n, true);
            nodes.RemoveChild(n);
            currentNode.AppendChild(newNode);
        }
    }
    return null;
}

As you can see my nodes also contain "type" and "name". 如您所见,我的节点还包含“type”和“name”。 Types are used to determine if a nodes is a "folder" or a "product". 类型用于确定节点是“文件夹”还是“产品”。

My problem is that this dosn't return the correct XML. 我的问题是,这不会返回正确的XML。 If I remove the nodes.RemoveChild(n) in the last section then it works great but I whant to remove the children (products, type=1) that I know haven't got any children. 如果我删除了最后一节中的nodes.RemoveChild(n),那么它的效果很好,但是我想删除我知道没有孩子的子项(products,type = 1)。

If this code is run. 如果运行此代码。 I only get a few nodes. 我只得到几个节点。

I would take a different approach to the problem. 我会采取不同的方法来解决这个问题。 You are now trying to modify an existing document by moving nodes around which is rather complex. 您现在正尝试通过移动相对复杂的节点来修改现有文档。 I would parse the original document, store it in some data structure and write it again to another location. 我会解析原始文档,将其存储在某些数据结构中,然后再将其写入另一个位置。

Your data structure would look something like this: 您的数据结构如下所示:

public class Node
{
    public SomeClass NodeData { get ; set; }
    public List<Node> Children { get; }
}

where SomeClass is a typed object that holds the relevant data for a single node. 其中SomeClass是一个类型化对象,用于保存单个节点的相关数据。 And then your code should look like this: 然后你的代码应该是这样的:

Node rootNode = ParseXml(...);
WriteStructuredXml(rootNode);

Both of these methods are not hard to write. 这两种方法都不难写。 This way you divide the problem into two smaller, easier problems. 这样您就可以将问题分成两个更小,更简单的问题。

this code does the job. 这段代码完成了这项工作。 Hope it's clear enough 希望它足够清楚

public class Node
{
    public Node()
    {
        Children = new List<Node>();
    }

    public int Id;

    public int ParentId;

    public List<Node> Children;

    public Node Parent;

    public static Node Deserialize(XmlElement xNode)
    {
        Node n = new Node();
        XmlElement xId = xNode.SelectSingleNode("id") as XmlElement;
        n.Id = int.Parse(xId.InnerText);
        XmlElement xParent = xNode.SelectSingleNode("parent") as XmlElement;
        n.ParentId = int.Parse(xParent.InnerText);
        return n;
    }

    public void AddChild(Node child)
    {
        Children.Add(child);
        child.Parent = this;
    }

    public void Serialize(XmlElement xParent)
    {
        XmlElement xNode = xParent.OwnerDocument.CreateElement("node");
        XmlElement xId = xParent.OwnerDocument.CreateElement("id");
        xId.InnerText = Id.ToString();
        xNode.AppendChild(xId);
        XmlElement xParentId = xParent.OwnerDocument.CreateElement("parent");
        xParentId.InnerText = ParentId.ToString();
        xNode.AppendChild(xParentId);
        foreach (Node child in Children)
            child.Serialize(xNode);
        xParent.AppendChild(xNode);
    }
}

public static XmlDocument DeserializeAndReserialize(XmlDocument flatDoc)
{
    Dictionary<int, Node> nodes = new Dictionary<int, Node>();
    foreach (XmlElement x in flatDoc.SelectNodes("//node"))
    {
        Node n = Node.Deserialize(x);
        nodes[n.Id] = n;
    }

    // at the end, retrieve parents for each node
    foreach (Node n in nodes.Values)
    {
        Node parent;
        if (nodes.TryGetValue(n.ParentId, out parent))
        {
           parent.AddChild(n);
        }
    }

    XmlDocument orderedDoc = new XmlDocument();
    XmlElement root = orderedDoc.CreateElement("root");
    orderedDoc.AppendChild(root);
    XmlElement xnodes = orderedDoc.CreateElement("nodes");
    foreach (Node n in nodes.Values)
    {
        if (n.Parent == null)
            n.Serialize(xnodes);
    }
    root.AppendChild(xnodes);
    return orderedDoc;
}

Here's some code that gets all the nodes with the name 'node': 这里有一些代码可以获得名为'node'的所有节点:

    public static IEnumerable<XmlNode> GetNodes(XmlDocument xdoc)
    {
        var nodes = new List<XmlNode>();

        Queue<XmlNode> toProcess = new Queue<XmlNode>();

        if (xdoc != null)
        {
            foreach (var node in GetChildElements(xdoc))
            {
                toProcess.Enqueue(node);
            }
        }

        do
        {
            //get a node to process
            var node = toProcess.Dequeue();

            // add node to found list if name matches
            if (node.Name == "node")
            {
                nodes.Add(node);
            }

            // get the node's children
            var children = GetChildElements(node);

            // add children to queue.
            foreach (var n in children)
            {
                toProcess.Enqueue(n);
            }

            // continue while queue contains items.
        } while (toProcess.Count > 0);


        return nodes;
    }

    private static IEnumerable<XmlNode> GetChildElements(XmlNode node)
    {
        if (node == null || node.ChildNodes == null) return new List<XmlNode>();

        return node.ChildNodes.Cast<XmlNode>().Where(n=>n.NodeType == XmlNodeType.Element);
    }

Then you'll need to move the nodes around based on parent-child rel'ships. 然后,您需要根据父子关系移动节点。 See @PierrOz's answer. 请参阅@ PierrOz的回答。

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

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