So this is a post as per request in a different post on stack overflow: https://stackoverflow.com/questions/62412713/why-does-my-c-sharp-xml-code-only-work-when-i-enumerate-variable-enumerable. The goal of this code is to re-format an XML-file containing a treeview of a sort of folder structure. The XML is of the following format where every folder (Main or sub) is contained as a direct child of the root:
<Processen>
<process-group id="12345" name="Main1">
<members>
<process id="23456" />
</members>
</process-group>
<process-group id="34567" name="Main1/Subfolder1">
<members>
<process id="45678" />
</members>
</process-group>
</Processen>
// Etcetera
It needs to be:
<Processen>
<process-group id="12345" name="Main1">
<members>
<process id="23456" />
<process-group id="34567" name="Subfolder1">
<members>
<process id="45678" />
</members>
</process-group>
</members>
</process-group>
</Processen>
The code I created to accomplish this is real messy, I had to figure a lot out as I am an absolute beginner at coding. The code checks to see if a node is in fact a folder (process-group in the XML), then whether that folder has a name containing a slash (indicating it is a subfolder) and if so; Check if parent folder exists -> Yes: move item to that node, No: Create xml root to folder path and then move item to that node. Don't get confused by the node name and the node attribute called name. This is something I cannot influence.
The code for this:
// Load XML tree
string sFile = @"FilePath";
XmlDocument doc = new XmlDocument();
doc.Load(sFile);
// Read nodes into nodelist
var n = doc.DocumentElement.SelectNodes("//*").OfType<XmlNode>().ToList();
// Build actual tree (dus childfolders in parentfolders zetten)
foreach (XmlNode x in n) // For each node
{
XmlElement xParentEle = x as XmlElement; // Convert to XmlElement to be able to check if attribute exists
if((xParentEle != null) && xParentEle.HasAttribute("name")) // If attribute (name) exists
{
if(x.Name == "process-group") // If name (= node type) process-group, these are the folders.
{
if (x.Attributes["name"].Value.ToString().Contains("/")) // If attribute name contains a '/' and is therefore a nested folder (I.e.: name="Mainfolder/subfolder")
{
string[] folders = x.Attributes["name"].Value.ToString().Split('/'); // Split into individual folder names
for(int i = folders.Length-2; i >= 0; i--) // Go through each name, starting at deepest level and work towards root. Check if folder that should be parent exists. If yes, Move node and delete original. If no, make node with that name and path.
{
if(x.ParentNode != null) // This is to prevent errors if current node has no parent as next step assumes parent exists.
{
if (x.ParentNode.SelectSingleNode("//process-group[@name='" + folders[i] + "']") != null) // If parent node of deepest folder has currently examined name. This is separate from nodes which contain multiple splits/levels as single split nodes only have to be moved. Multiple level splits have to have their paths created first which neccesitates a different approach.
{
XmlNode tempNode = x.Clone(); // Temporary clone of node that is to be moved
tempNode.Attributes["name"].Value = folders[folders.Length - 1]; // Change attribute name to only folder name (no more mainfolder/subfolder, just subfolder)
XmlNode removeNode = doc.SelectSingleNode("//process-group[@name='" + x.Attributes["name"].Value.ToString() + "']"); // Create reference to original node, it must be deleted and there should only be 1 with a particular name
doc.SelectSingleNode("/Processen").RemoveChild(removeNode); // Select parentnode and delete childnode
doc.SelectSingleNode("//process-group[@name='" + folders[i] + "']").AppendChild(tempNode); // Add the temporary node to appropriate parent folder
}
else // If the appropriate parent node does NOT exist. In this case the path and structure has to be created until the first separation. This is done by creating dummy nodes with the appropriate name and add them into the path
{
string tempXPath = "/Processen"; // Set temporary xPath to root node. This string is added onto as path grows.
foreach (string folder in folders) // For each folder contained in the necessary path, Starting at the highest level (directly underneath root)
{
if (doc.SelectSingleNode(tempXPath + "/process-group[@name='" + folder + "']") == null) // If requested node at requested location does not exist.
{
if (folder != folders[folders.Length - 1]) // If requested node is not the deepest level (Deepest level has to be copied/moved, until then they're empty/new nodes)
{
XmlNode newNode = x.Clone(); // Make new node
newNode.RemoveAll(); // Empty new node
XmlAttribute nameAttr = doc.CreateAttribute("name"); // Make node attribute "name"
nameAttr.Value = folder; // Make attribute value into name of requested folder (this is the name of a folder in the path, ex. mainfolder/ SUBFOLDERNAME / subfolder, then SUBFOLDERNAAM)
newNode.Attributes.Append(nameAttr); // Add attribute to new node
doc.SelectSingleNode(tempXPath).AppendChild(newNode); // Add new folder node to appropriate location in original document
tempXPath += "/process-group[@name='" + folder + "']"; // Go 1 level deeper with the xPath
}
else
{
XmlNode tempNode = x.Clone(); // Temporary clone of node that is to be moved
tempNode.Attributes["name"].Value = folders[folders.Length - 1]; // Change attribute name to only folder name (no more mainfolder/subfolder, just subfolder)
XmlNode removeNode = doc.SelectSingleNode("//process-group[@name='" + x.Attributes["name"].Value.ToString() + "']"); // Create reference to original node, it must be deleted and there should only be 1 with a particular name
string parentXPath = tempXPath.Substring(0, tempXPath.LastIndexOf('/')); // Remove last node from xPath to arrive at parent node. This is different from the 1 split nodes as in that case parent node is always root
doc.SelectSingleNode(parentXPath).RemoveChild(removeNode); // Select parentnode and delete childnode
doc.SelectSingleNode("//process-group[@name='" + folders[i] + "']").AppendChild(tempNode); // Add temporary node to appropriate folder
}
}
}
}
}
}
}
}
}
}
I am absolutely certain that this is not the best way of approaching this problem but this is the best I could think of. Apparently I've learned by now you can have a "live" node list where changes are reflected in the original document, but this lead to other difficulties as you can read in the original post linked above. I am curious if you guys can come up with better ideas!