Select multiple elements with Linq to XML - c#

I have a C# application and need to extract multiple elements from a Linq to XML collection.
I have the following extract from an XML file
<SNS>
<uniqueSystem><system>49</system><label>Engines</label>
<uniqueSubsystem><subsystem>30</subsystem><label>APU</label>
<uniqueUnit><unit>00</unit><label>Starter</label>
</uniqueUnit>
</uniqueSubsystem>
</uniqueSystem>
<uniqueSystem><system>50</system><label>Hydraulics</label>
<uniqueSubsystem><subsystem>30</subsystem><label>Reservoir</label>
<uniqueUnit><unit>00</unit><label>Pump</label>
</uniqueUnit>
</uniqueSubsystem>
</uniqueSystem></SNS>
I need to extract the values from within each 'uniqueSystem' element. So in the example above, under the 'SNS' element there are 2 'uniqueSystem' elements, and within each of these there are 'uniqueSubsystem' elements and 'uniqueUnit' elements each with 'label' elements. I need to extract this data to build a TreeView.
My problem is extracting multiple elements using Linq. How do i do this?
At the moment i have
var item = from items in doc.Descendants("SNS").Descendants("uniqueSystem").Descendants("system")
orderby items.Value
select items.Descendants("uniqueSystem");
I think this will give me a collection of the 'uniqueSystem' elements, from which i now need to extract the values of the multiple elements within. Can anybody please help?
My next attmpt is as follows, but this is giving me a null reference exception:
var item = from items in doc.Descendants("SNS").Descendants("uniqueSystem").Descendants("system")
orderby items.Value
select items.Descendants("uniqueSystem");
foreach (var e in item)
{
string sys = e.Descendants("system").FirstOrDefault().Value;
string sysLabel = e.Descendants("system").Descendants("label").FirstOrDefault().Value;
string subsys = e.Descendants("subsystem").FirstOrDefault().Value;
string subsysLabel = e.Descendants("subsystem").Descendants("label").FirstOrDefault().Value;
string unit = e.Descendants("unit").FirstOrDefault().Value;
string unitLabel = e.Descendants("unit").Descendants("label").FirstOrDefault().Value;
buildSystemNodes(sys, sysLabel);
//getSubSystems(myitem);
}

So the code I have written below will generate the tree view. The treeview class that I am using is in the namespace System.Web.UI.WebControls. The function is however O(n^3).
public void ProcessXml(string document)
{
var doc = XDocument.Parse(document, LoadOptions.None);
var uniqueSystemList = doc.Element("SNS").Elements();
var treeView = new TreeView();
string value = string.Empty;
string text = string.Empty;
foreach (var uniqueSystem in uniqueSystemList)
{
value = uniqueSystem.Element("label").Value.ToString();
text = uniqueSystem.Element("system").Value.ToString();
var uniqueSystemNode = new TreeNode(text, value);
var uniqueSubsystemList = uniqueSystem.Elements("uniqueSubsystem");
foreach (var uniqueSubSystem in uniqueSubsystemList)
{
value = uniqueSubSystem.Element("label").Value.ToString();
text = uniqueSubSystem.Element("subsystem").Value.ToString();
var uniqueSubSystemNode = new TreeNode(text, value);
var uniqueUnitList = uniqueSubSystem.Elements("uniqueUnit");
foreach (var uniqueUnit in uniqueUnitList)
{
value = uniqueUnit.Element("label").Value.ToString();
text = uniqueUnit.Element("unit").Value.ToString();
var uniqueUnitNode = new TreeNode(text, value);
uniqueSubSystemNode.ChildNodes.Add(uniqueUnitNode);
}
uniqueSystemNode.ChildNodes.Add(uniqueSubSystemNode);
}
treeView.Nodes.Add(uniqueSystemNode);
}
}

Check if this kind of code will work out for your requirement
var doc = XDocument.Load(#"..\XMLFile1.xml");
var res = doc.XPathSelectElements("/SNS/uniqueSystem");
foreach (var item in res)
{
var us = new UniqueSystem
{
System = int.Parse(item.Element("system").Value),
Label = item.Element("label").Value
};
if (item.Element("uniqueSubsystem") != null)
{
// process the logic here
}
}
Where you can have models like the ones shown below to bind to
class UniqueSystem
{
public int System { get; set; }
public string Label { get; set; }
public List<uniqueSubsystem> SubSystems { get; set; }
}
class uniqueSubsystem
{
public int subsystem { get; set; }
public string Label { get; set; }
public List<uniqueUnit> Units { get; set; }
}
class uniqueUnit
{
public int unit { get; set; }
public string label { get; set; }
}

By the following code you can get the uniqueSubsystem
XmlNodeList nodes = xml.SelectNodes("SNS/uniqueSystem/uniqueSubsystem");
foreach (XmlNode i in nodes)
{
Response.Write(i.InnerXml);
}
If you want the nodes:
XmlNodeList labels1 = xml.SelectNodes("SNS/uniqueSystem/uniqueSubsystem/label");
XmlNodeList labels2 = xml.SelectNodes("SNS/uniqueSystem/uniqueSubsystem/uniqueUnit/label");
Response.Write("SNS/uniqueSystem/uniqueSubsystem/label<br />");
foreach (XmlNode i in labels1)
{
Response.Write(i.InnerText+"<br />");
}
Response.Write("<br />SNS/uniqueSystem/uniqueSubsystem/uniqueUnit/label<br />");
foreach (XmlNode i in labels2)
{
Response.Write(i.InnerText+"<br />");
}
Let me know if this helped you.

Related

Populate hierarchy in Dictionary

I need to build a hierarchy and I am using a dictionary.
I read strings randomly when I am trying to build this
and they have this format:
address.city.streetname.housenumber
address.areacode
address.city.streetname.coAddress
I have a problem figuring out how to populate the entire hierarchy
This is what I have done:
public class JsonElement
{
public string parent { get; set; }
public string name { get; set; }
public List<JsonElement> childrenJsonElements { get; set; }
}
var dictionaryHierarchy = new Dictionary<string, JsonElement>();
List<string> stringList = new List<string>()
{ "address.city.streetname.housenumber",
"address.areacode",
"address.city.streetname.coAddress"};
foreach(string element in stringList)
{
string[] tagsStringArray = element.Split('.');
if (!dictionaryHierarchy.ContainsKey(tagsStringArray[0]))
{
dictionaryHierarchy.Add(tagsStringArray[0], new JsonElement());
}
dictionaryHierarchy = AddElementsToHierarchy();
}
private static Dictionary<string, JsonElement> AddElementsToHierarchy(Dictionary<string,
JsonElement> dictionaryHierarchy, string element)
{
JsonElement jsonElement = new JsonElement();
string[] tagsStringArray = element.Split('.');
if (tagsStringArray.Length < 2)
{
return dictionaryJsonHierarchy;
}
jsonElement = dictionaryHierarchy[tagsStringArray[0]];
int ix = 1;
while (ix < tagsStringArray.Length)
{
if (jsonElement.name != tagsStringArray[ix])
{
jsonElement.parent = tagsStringArray[ix-1];
jsonElement.name = tagsStringArray[ix];
}
else
{
; // This part is for adding children
}
ix++;
}
return dictionaryHierarchy;
}
You have a tree structure made up of JsonElement nodes. This structure is the only data structure you need. Let's redefine JsonElement:
public class JsonElement
{
public string Parent { get; set; }
public string Name { get; set; }
public List<JsonElement> Children { get; } = new List<JsonElement>();
}
We made the properties PascalCase. This is the usual naming convetion. The Children are a read-only property with an initializer which instantiates the list.
As suggested, we add some more examples:
var input = new[] {
"address.city.streetname.housenumber",
"address.areacode",
"address.city.streetname.coAddress",
"person.name.firstname",
"person.name.lastname"
};
Now, we have two different elements at the start of the hierarchy. To enable this scenario, we add a neutral root element with a null name.
var root = new JsonElement();
foreach (string s in input) {
AddElements(root, s.Split('.'));
}
Now, let's create the hierarchy.
Adding elements consists of walking down the tree structure by following the tags (names). If one is missing, we add it.
private static void AddElements(JsonElement node, string[] elements)
{
foreach (string element in elements) {
var child = node.Children.Find(child => child.Name == element);
if (child == null) {
child = new JsonElement {
Parent = node.Name,
Name = element
};
node.Children.Add(child);
}
node = child; // Walk down the tree
}
}
We can test the result with this recursive method:
private static void PrintChildren(JsonElement node, int level = 0)
{
string indent = new String(' ', 4 * level);
foreach (var child in node.Children) {
Console.WriteLine($"{indent}{child.Name}, Parent = {child.Parent}");
PrintChildren(child, level + 1);
}
}
Called with PrintChildren(root); it prints:
address, Parent =
city, Parent = address
streetname, Parent = city
housenumber, Parent = streetname
coAddress, Parent = streetname
areacode, Parent = address
person, Parent =
name, Parent = person
firstname, Parent = name
lastname , Parent = name
See also:
Data Structure and Algorithms - Tree
Tree (data structure)

Search in hierarchy tree to get tree with only result node

I have hierarchy tree, each element has a list of children and so on.
This is the class:
public class HierarchyItem
{
public int? HierarchyID { get; set; }
public string label { get; set; }
public List<HierarchyItem> children { get; set; }
public int Level { get; set; }
public int ParentID { get; set; }
}
I am trying to search for all the nodes that their label contains my search term.
I already managed to get the nodes that match my search term.
In addition i created a recursion to get all the parents (until the top node) for every node that match my search term.
The problem is that in this way i get a reverse tree:
Every children has parent that has parent and so on instead of getting parent with children that has a children and so on.
Any Idea? How to act?
I am not sure it will help, but this is the relevant code.
HierarchyItem result = new HierarchyItem();
var fullHierarchies = _entities.Hierarchies.Where(p => !p.Deleted).ToList();
var hierarchiesResult = _entities.Hierarchies.Where(p => !p.Deleted && p.Name.Contains(searchTerm)).ToList();
List<HierarchyItemWithParentAsHierarchyItem> itemsWithTrees = new List<HierarchyItemWithParentAsHierarchyItem>();
var hierarchyTop = new HierarchyItemWithParentAsHierarchyItem
{
HierarchyID = null,
label = "Organization",
Code = "0",
Path = string.Empty,
Level = 0,
Parent = null
};
foreach (var item in hierarchiesResult)
{
var hierarchy = new HierarchyItemWithParentAsHierarchyItem
{
HierarchyID = item.HierarchyID,
label = item.Name,
Code = item.Code,
Path = string.Empty,
Level = item.Level.Value,
};
hierarchy.Parent.Add(GetParent(fullHierarchies, item, hierarchyTop));
itemsWithTrees.Add(hierarchy);
}
private HierarchyItemWithParentAsHierarchyItem GetParent(List<CloudEntities.Hierarchy> fullHierarchies, CloudEntities.Hierarchy item, HierarchyItemWithParentAsHierarchyItem hierarchyTop)
{
try
{
HierarchyItemWithParentAsHierarchyItem tempHierarchyItem;
CloudEntities.Hierarchy tempHierarchy = fullHierarchies.FirstOrDefault(x => x.HierarchyID == item.ParentID);
if (tempHierarchy == null)
return hierarchyTop;
else
{
tempHierarchyItem = new HierarchyItemWithParentAsHierarchyItem()
{
HierarchyID = tempHierarchy.HierarchyID,
label = tempHierarchy.Name,
Code = tempHierarchy.Code,
Path = string.Empty,
Level = tempHierarchy.Level.Value
};
tempHierarchyItem.Parent.Add(GetParent(fullHierarchies, tempHierarchy, hierarchyTop));
}
return tempHierarchyItem;
}
catch (Exception ex)
{
}
}

how to transform the following in C#?

I have the following model
public class Node
{
public int AutoIncrementId { get; set; }
public string Text { get; set; }
public List<Node> Nodes { get; set; }
...//other propeties
}
I want to transform the data into the following model,
public class TreeView
{
public int Id {get; set;}
public string Text {get; set;}
public List<TreeView> Items {get; set;}
}
I started with the following, but then realised how am I going to know when to stop?
the variable test holds the node data
var items = test.Data.Select(x => new TreeViewItemModel
{
Id = x.AutoIncrementId.ToString(),
Text = x.Text,
Items = x.Nodes.Select(y=> new TreeViewItemModel(
{
Id = y.AutoIncrementId.ToString(),
Text = y.Text,
Items = //do I keep going?
}));
}
);
You can use recursion to do that:
public TreeView ConvertToTreeView(Node node)
{
TreeView tv = new TreeView();
tv.Id = node.AutoIncrementId;
tv.Text = node.Text;
if (node.Nodes != null && node.Nodes.Count > 0)
{
tv.Items = new List<TreeView>();
node.Nodes.ForEach(x => tv.Items.Add(ConvertToTreeView(x)));
}
return tv;
}
For clarity and simplicity, this works nicely:
public TreeView ConvertNode(Node rootNode)
{
var tree = new TreeView
{
Id = rootNode.AutoIncrementId,
Text = rootNode.Text,
Items = new List<TreeView>()
};
if (rootNode.Nodes != null)
{
foreach (var node in rootNode.Nodes)
{
tree.Items.Add(ConvertNode(node));
}
}
return tree;
}
I prefer this form.
public TreeView ConvertToTreeView(Node node)
{
return new TreeView
{
Id = node.AutoIncrementId;
Text = node.Text;
Items = node.Nodes.Select(ConvertToTreeView).ToList()
};
}
Edit: Yes Baldrick, I did :P and
public TreeView ConvertToTreeView(Node node)
{
return new TreeView
{
Id = node.AutoIncrementId;
Text = node.Text;
Items = node.Nodes != null
? node.Nodes.Select(ConvertToTreeView).ToList()
: new List<TreeView>()
};
}
Just doesn't look as nice.

Unable to read attribute in Linq to xml, error: Object reference not set to an instance of an object

I am new in Linq to xml. My xml string is as follows :
<root>
<add>
<item id="jqg1" moduleid="F0736590-84A2-4795-BC5D-0056606F2446" view="1" add="1" edit="0" del="0" />
</add>
<update>
<item id="jqg3" moduleid="3C414435-DBBE-4B4E-A790-14B67F050EB0" view="1" add="1" edit="1" del="0" />
</update>
</root>
coming from jquery to C# which is placed inside texbox(textbox Visible = false).
NOTE : element could be more than one in every and element.
Code to read xml string in C# on button click eevnt.
string paramXML = txtXmlStr.Text;
var l_sID = "";
var l_sMODID = "";
var l_sVIEW = "";
var l_sADD = "";
var l_sEDIT = "";
var l_sDEL = "";
if (!string.Empty.Equals(paramXML))
{
var l_oXDoc = XDocument.Parse(paramXML.ToLower());
==> var addParams = from addParam in l_oXDoc.Element("add").Elements("item")
select new
{
l_sID = addParam.Attribute("id").Value,
l_sMODID = addParam.Attribute("moduleId").Value,
l_sVIEW = addParam.Attribute("view").Value,
l_sADD = addParam.Attribute("add").Value,
l_sEDIT = addParam.Attribute("edit").Value,
l_sDEL = addParam.Attribute("del").Value,
};
foreach (var oParam in addParams)
{
string id = oParam.l_sID.Trim();
string modid = oParam.l_sMODID.Trim();
string view = oParam.l_sMODID.Trim();
string add = oParam.l_sMODID.Trim();
string edit = oParam.l_sMODID.Trim();
string del = oParam.l_sMODID.Trim();
}
I am getting above error on arrow which I already specified in code. How should I resolve this error???
*NOTE:*My goal is to read <Add> element's <item> element data into one variable and I also want to do same for <update> element. Is that possible.... everyones suggestion, ideas and code is welcome....
You need to get the root element first. Change:
var addParams = from addParam in l_oXDoc.Element("add").Elements("item")
To
var addParams = from addParam in
l_oXDoc.Descendants("root").First().Element("add").Elements("item")
Attributes/Elements names are case sensitive.
Replace
l_sMODID = addParam.Attribute("moduleId").Value,
With
l_sMODID = addParam.Attribute("moduleid").Value,
That's because attributes in your input XML is moduleid="F0736590-84A2-4795-BC5D-0056606F2446", not moduleId.
Working code:
var doc = XDocument.Load("Input.txt");
Func<XElement, IEnumerable<Item>> query = e => e.Elements("item")
.Select(x => new Item
{
Id = (string)x.Attribute("id"),
ModuleId = (string)x.Attribute("moduleid"),
View = (bool)x.Attribute("view"),
Add = (bool)x.Attribute("add"),
Edit = (bool)x.Attribute("edit"),
Delete = (bool)x.Attribute("del")
});
var addItems = query(doc.Root.Element("add")).ToList();
var updateItems = query(doc.Root.Element("update")).ToList();
Item class declaration:
public class Item
{
public string Id { get; set; }
public string ModuleId { get; set; }
public bool View { get; set; }
public bool Add { get; set; }
public bool Edit { get; set; }
public bool Delete { get; set; }
}

parse xml children nodes

What is the best way to parse XML children nodes into a specific list? This is a small example of the XML.
<Area Name="Grey Bathroom" IntegrationID="3" OccupancyGroupAssignedToID="141">
<Outputs>
<Output Name="Light/Exhaust Fan" IntegrationID="46" OutputType="NON_DIM" Wattage="0" />
</Outputs>
</Area>
I want to create a list or something that will be called the Area Name and hold the information of the Output Name and IntegrationID. So I can call the list and pull out the Output Name and IntegrationID.
I can create a list of all Area Names and then a list of Outputs but cannot figure out how to create a list that will be called "Grey Bathroom" and hold the output "Light/Exhaust Fan" with an ID of 46.
XDocument doc = XDocument.Load(#"E:\a\b.xml");
List<Area> result = new List<Area>();
foreach (var item in doc.Elements("Area"))
{
var tmp = new Area();
tmp.Name = item.Attribute("Name").Value;
tmp.IntegrationID = int.Parse(item.Attribute("IntegrationID").Value);
tmp.OccupancyGroupAssignedToID = int.Parse(item.Attribute("OccupancyGroupAssignedToID").Value);
foreach (var bitem in item.Elements("Outputs"))
{
foreach (var citem in bitem.Elements("Output"))
{
tmp.Outputs.Add(new Output
{
IntegrationID = int.Parse(citem.Attribute("IntegrationID").Value),
Name = citem.Attribute("Name").Value,
OutputType = citem.Attribute("OutputType").Value,
Wattage = int.Parse(citem.Attribute("Wattage").Value)
});
}
}
result.Add(tmp);
}
public class Area
{
public String Name { get; set; }
public int IntegrationID { get; set; }
public int OccupancyGroupAssignedToID { get; set; }
public List<Output> Outputs = new List<Output>();
}
public class Output
{
public String Name { get; set; }
public int IntegrationID { get; set; }
public String OutputType { get; set; }
public int Wattage { get; set; }
}
The example uses an anonymous type. You could (and I warmly advice you to) use your own.
var doc = XDocument.Parse(xml);
var areaLists = doc.Elements("Area").
Select(e => e.Descendants("Output").
Select(d => new
{
Name = (string) d.Attribute("Name"),
Id = (int) d.Attribute("IntegrationID")
}).
ToArray()).
ToList();

Categories