XML树生成

时间:2010-09-07 10:13:45

标签: c# xml

我需要构建一个“平面”的XML菜单。

当前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>

需要重新编码此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>

我有以下代码来尝试完成此任务:

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;
}

正如您所看到的,我的节点还包含“type”和“name”。类型用于确定节点是“文件夹”还是“产品”。

我的问题是这不会返回正确的XML。如果我删除了最后一节中的nodes.RemoveChild(n),那么它的效果很好,但是我想删除我知道没有孩子的子项(products,type = 1)。

如果运行此代码。我只收到几个节点。

3 个答案:

答案 0 :(得分:4)

我会对问题采取不同的方法。您现在正尝试通过移动相对复杂的节点来修改现有文档。我会解析原始文档,将其存储在某些数据结构中,然后再将其写入另一个位置。

您的数据结构如下所示:

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

其中SomeClass是一个类型化对象,用于保存单个节点的相关数据。然后你的代码应该是这样的:

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

这两种方法都不难写。这样,您可以将问题分成两个更小,更容易的问题。

答案 1 :(得分:2)

这段代码完成了这项工作。希望它足够清楚

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;
}

答案 2 :(得分:0)

以下是一些获取名称为“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);
    }

然后,您需要根据父子关系移动节点。请参阅@ PierrOz的回答。