我需要构建一个“平面”的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)。
如果运行此代码。我只收到几个节点。
答案 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的回答。