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();
Related
I need to sort my csv file alphabetically and not show the ones that it says "hidden" for (aka. client 4 and client 5) this is the code:
static void Main(string[] args)
{
ReadCSFVFile();
Console.WriteLine();
}
static void ReadCSFVFile()
{
var lines = File.ReadAllLines("Navigation.txt");
var list = new List<Company>();
foreach (var line in lines)
{
var values = line.Split(';' );
var company = new Company() { ID = values[0], MenuName = values[1], ParentID = values[2], IsHidden = values[3], LinkURL = values[4] };
list.Add(company);
}
list.ForEach(x => Console.WriteLine($"{x.ID}\t {x.MenuName}\t {x.ParentID}\t {x.IsHidden}\t {x.LinkURL}"));
}
public class Company
{
public string ID { get; set; }
public string MenuName { get; set; }
public string ParentID { get; set; }
public string IsHidden { get; set; }
public string LinkURL { get; set; }
}
and this is the csv file:
ID;MenuName;ParentID;isHidden;LinkURL
1;Company;NULL;False; /company
2;About Us;1;False; /company/aboutus
3;Mission;1;False; /company/mission
4;Team;2;False; /company/aboutus/team
5;Client 2;10;False; /references/client2
6;Client 1;10;False; /references/client1
7;Client 4;10;True; /references/client4
8;Client 5;10;True; /references/client5
10;References;NULL;False; /references
The below should achieve this for you. I've commented the parts I've added to help out.
list.OrderBy(x => x.MenuName) // Order alphabetically based on MenuName
.Where(x => x.IsHidden != "True") // Filter only for non-hidden items
.ToList().ForEach(
x => Console.WriteLine($"{x.ID}\t {x.MenuName}\t {x.ParentID}\t{x.IsHidden}\t {x.LinkURL}"));
I'm having an issue presenting my nested collection in a WPF Datagrid.
bellow is the code that is giving me the desired result, but I wonder if it is possible to make it simpler?
public async Task LoadRecepi(short id)
{
Recepi = await _recepiDataService.Get(id);
var flat = new List<FlatRecepi1>();
foreach (var step in Recepi.Step)
{
flat.Add(new FlatRecepi1 {
RecepiId = step.RecepiId,
StepId = step.SPTagId,
Activity = step.Activity,
PVTagName = step.PVTag.Name
});
foreach (var node in step.Nodes)
{
flat.Add(new FlatRecepi1
{
StepId = node.SPTagId,
SPTagName = node.SPTag.Name,
PVTagName = node.PVTag.Name
});
}
}
}
thankyou so much for your help.
public class FlatRecepi1
{
public short RecepiId { get; set; }
public short StepId { get; set; }
public Activity Activity { get; set; }
public short NodeId { get; set; }
public string StepName { get; set; }
public string PVTagName { get; set; }
public string SPTagName { get; set; }
public Operator Operator { get; set; }
}
You can use a combination of Enumerable.SelectMany and Enumerable.Prepend.
The following code will project out each step's nodes into a collection of FlatRecepi1 and then prepend the FlatRecepi1 corresponding to the step at the start of the collection. Finally, the SelectMany flattens this "collection of collections" into a single list. This should give you the same ordering as the current code.
var flat = Recepi.Step.SelectMany(step =>
step.Nodes.Select(node => new FlatRecepi1 {
StepId = node.SPTagId,
SPTagName = node.SPTag.Name,
PVTagName = node.PVTag.Name
}).Prepend(new FlatRecepi1 {
RecepiId = step.RecepiId,
StepId = step.SPTagId,
Activity = step.Activity,
PVTagName = step.PVTag.Name,
})
).ToList();
If Prepend is not available to you because you are using an older framework, we can achieve the same with Enumerable.Concat:
var flat = Recepi.Step.SelectMany(step =>
new [] {
new FlatRecepi1 {
RecepiId = step.RecepiId,
StepId = step.SPTagId,
Activity = step.Activity,
PVTagName = step.PVTag.Name,
}
}.Concat(
step.Nodes.Select(node => new FlatRecepi1 {
StepId = node.SPTagId,
SPTagName = node.SPTag.Name,
PVTagName = node.PVTag.Name
})
)
).ToList();
I'm trying to create an object and insert to the database but keep getting the same error no matter what I try.
The row that I get the error on is ColumnGroupTest.ValidValues.Add(memberComment1); the error is
error message
NullReferenceException was unhandled by user code
my models
public class StoreColumnName
{
public int Id { get; set; }
public string StoreColumnGroupName { get; set; }
public string ColumnName { get; set; }
public string ColumnType { get; set; }
public List<StoreValidValue> ValidValues { get; set; }
}
public class StoreValidValue
{
public int Id { get; set; }
public string ValidValue { get; set; }
public StoreColumnName StoreColumnName { get; set; }
}
my controller
public ActionResult Index()
{
XDocument document = XDocument.Load(#"C:\Users\Physical.xml");
var result = document.Descendants("ColumnGroup");
foreach(var item in result){
var ColumnGroupName = item.Attribute("name").Value;
var Columns = item.Descendants("Column");
foreach (var itemColumn in Columns)
{
StoreColumnName ColumnGroup = new StoreColumnName();
var ColumnGroupTest = new StoreColumnName
{
StoreColumnGroupName = ColumnGroupName,
ColumnName = itemColumn.Attribute("name").Value,
ColumnType = itemColumn.Attribute("type").Value,
Id = 11
};
var ValidValues = itemColumn.Descendants("ValidValues");
var Values = ValidValues.Descendants("Value");
foreach (var Value in Values)
{
var memberComment1 = new StoreValidValue
{
StoreColumnName = ColumnGroupTest,
ValidValue = Value.Value,
Id = 101
};
ColumnGroupTest.ValidValues.Add(memberComment1);
}
}
}
return View();
}
(I gladly take tips on what I can improve when asking for help/guiding here).
Can anyone help ?
The issue that you're having is that you don't initialize your ValidValues property to a list. By default, those types of properties initialize to null unless you specify differently.
The best approach is to add that initialization to your constructor of that object.
public StoreColumnName() {
this.ValidValues = new List<StoreValidValue>();
}
I am having trouble deserializing a list of objects. I can get just one object to serialize into an object but cannot get the list. I get no error it just returns an empty List. This is the XML that gets returned:
<locations>
<location locationtype="building" locationtypeid="1">
<id>1</id>
<name>Building Name</name>
<description>Description of Building</description>
</location>
</locations>
This is the class I have and I am deserializing in the GetAll method:
[Serializable()]
[XmlRoot("location")]
public class Building
{
private string method;
[XmlElement("id")]
public int LocationID { get; set; }
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("mubuildingid")]
public string MUBuildingID { get; set; }
public List<Building> GetAll()
{
var listBuildings = new List<Building>();
var building = new Building();
var request = WebRequest.Create(method) as HttpWebRequest;
var response = request.GetResponse() as HttpWebResponse;
var streamReader = new StreamReader(response.GetResponseStream());
TextReader reader = streamReader;
var serializer = new XmlSerializer(typeof(List<Building>),
new XmlRootAttribute() { ElementName = "locations" });
listBuildings = (List<Building>)serializer.Deserialize(reader);
return listBuildings;
}
}
Try this:
[XmlRoot("locations")]
public class BuildingList
{
public BuildingList() {Items = new List<Building>();}
[XmlElement("location")]
public List<Building> Items {get;set;}
}
Then deserialize the whole BuildingList object.
var xmlSerializer = new XmlSerializer(typeof(BuildingList));
var list = (BuildingList)xmlSerializer.Deserialize(xml);
I know this is an old(er) question, but I struggled with this today, and found an answer that doesn't require encapsulation.
Assumption 1: You have control over the source Xml and how it is constructed.
Assumption 2: You are trying to serialise the Xml directly into a List<T> object
You must name the Root element in the Xml as ArrayOfxxx where xxx is the name of your class (or the name specified in XmlType (see 2.))
If you wish your xml Elements to have a different name to the class, you should use XmlType on the class.
NB: If your Type name (or class name) starts with a lowercase letter, you should convert the first character to uppercase.
Example 1 - Without XmlType
class Program
{
static void Main(string[] args)
{
//String containing the xml array of items.
string xml =
#"<ArrayOfItem>
<Item>
<Name>John Doe</Name>
</Item>
<Item>
<Name>Martha Stewart</Name>
</Item>
</ArrayOfItem>";
List<Item> items = null;
using (var mem = new MemoryStream(Encoding.Default.GetBytes(xml)))
using (var stream = new StreamReader(mem))
{
var ser = new XmlSerializer(typeof(List<Item>)); //Deserialising to List<Item>
items = (List<Item>)ser.Deserialize(stream);
}
if (items != null)
{
items.ForEach(I => Console.WriteLine(I.Name));
}
else
Console.WriteLine("No Items Deserialised");
}
}
public class Item
{
public string Name { get; set; }
}
Example 2 - With XmlType
class Program
{
static void Main(string[] args)
{
//String containing the xml array of items.
//Note the Array Name, and the Title case on stq.
string xml =
#"<ArrayOfStq>
<stq>
<Name>John Doe</Name>
</stq>
<stq>
<Name>Martha Stewart</Name>
</stq>
</ArrayOfStq>";
List<Item> items = null;
using (var mem = new MemoryStream(Encoding.Default.GetBytes(xml)))
using (var stream = new StreamReader(mem))
{
var ser = new XmlSerializer(typeof(List<Item>)); //Deserialising to List<Item>
items = (List<Item>)ser.Deserialize(stream);
}
if (items != null)
{
items.ForEach(I => Console.WriteLine(I.Name));
}
else
Console.WriteLine("No Items Deserialised");
}
}
[XmlType("stq")]
public class Item
{
public string Name { get; set; }
}
Not sure how Building corresponds with the location you have in your xml, but to me it makes more sense if they're named equivalently. Instead of using a List use a LocationList, and it becomes:
[Serializable()]
[XmlRoot("locations")]
public class LocationCollection{
[XmlElement("location")]
public Location[] Locations {get;set;}
}
[Serializable()]
[XmlRoot("location")]
public class Location
{
[XmlElement("id")]
public int LocationID { get; set; }
[XmlAttribute("locationtype")]
public string LocationType {get;set;}
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("mubuildingid")]
public string MUBuildingID { get; set; }
}
You can then deserialize as follows:
var request = WebRequest.Create(method) as HttpWebRequest;
var response = request.GetResponse() as HttpWebResponse;
var streamReader = new StreamReader(response.GetResponseStream());
TextReader reader = streamReader;
var serializer = new XmlSerializer(typeof(LocationCollection),
new XmlRootAttribute() { ElementName = "locations" });
var listBuildings = (LocationCollection)serializer.Deserialize(reader);
return listBuildings;
I know, old question, but came across it when faced by a similar issue.
Building on #ricovox's answer and in context of the OP's question, this is the model I would use to serialize his xml:
[Serializable, XmlRoot("locations")]
public class BuildingList
{
[XmlArrayItem("location", typeof(Building))]
public List<Building> locations { get; set; }
}
[Serializable]
public class Building
{
public int LocationID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string MUBuildingID { get; set; }
public List<Building> GetAll()
{
...
}
}
The OP's mistake was to create the list item as the root
Use [XMLArray] for collection properties.
I am trying to populate an Objective and ObjectiveDetail objects. Here are the classes I have:
partial class Objective
{
public Objective() {
this.ObjectiveDetails = new List<ObjectiveDetail>();
}
public int ObjectiveId { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
}
public partial class ObjectiveDetail
{
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public string Text { get; set; }
public virtual Objective Objective { get; set; }
}
I'm currently populating the only the Objective object from this call:
var objectiveData = GetContent.GetType5();
var objectives = objectiveData.Select(o => new Objective {
Name = o.Name,
Text = o.Text}
);
The data looks like this:
Name Text
0600 header 1
0601 detail abc
0602 detail def
0603 detail ghi
0700 header 2
0701 detail xyz
Is there a way I could modify my LINQ so that only the data where the name field contents end in "00" goes into the Objective object (as it does now) and when the data where the name field contents end in "01" then it creates a new ObjectiveDetail object with "detail abc" etc going into the text field.
This is a picture of what the end result should look like:
A collection of Objectives
new Objective { name = "header 1",
ObjectiveDetails = A collection of ObjectiveDetails
name = "detail abc"
name = "detail def" etc.
Sure you can do that, using [string.EndsWith] method like:1
.Where(r=> r.Name.EndsWith("00"))
Modify your query as:
var objectives = objectiveData
.Where(r => r.Name.EndsWith("00"))
.Select(o => new Objective {
Name = o.Name,
Text = o.Text}
);
It's somewhat unclear what you are asking, but you can put complex logic inside the Select() if you need to:
var objectives = objectiveData.Select(o =>
{
var result = new Objective
{
Name = o.Name,
Text = o.Text
};
if (o.Name != null && o.Name.EndsWith("01"))
{
result.ObjectiveDetails.Add
(
new ObjectiveDetail
{
ObjectiveDetailId = o.ObjectiveId,
Name = o.Name,
Text = o.Text,
Objective = result
}
);
}
return result;
});
(Note that I'm guessing at what you need; you will need to correct the logic to do what you really want.)
Looks like you want to do some sort of conditional mapping. I like Matthew Watson's answer, but it's a bit unclear why he's always creating an Objective instance every time. Here's some LINQ-less code which I believe is more readable, and maps the way I think you'd want:
public class Mapper
{
public List<Objective> Objectives = new List<Objective>();
public class Objective
{
public int ObjectiveId { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
public Objective()
{
ObjectiveDetails = new List<ObjectiveDetail>();
}
}
public class ObjectiveDetail
{
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public string Text { get; set; }
public virtual Objective Objective { get; set; }
}
public void Assign()
{
var objectiveData = new[] // Hard-coded test data. We don't know what the type of each item in this list is, so I use an anonymous type
{
new {Name = "0600", Text = "Header 06"},
new {Name = "0601", Text = "06 Detail 01"},
new {Name = "0602", Text = "06 Detail 02"},
new {Name = "0603", Text = "06 Detail 03"},
new {Name = "0700", Text = "Header 07"},
new {Name = "0701", Text = "07 Detail 01"},
new {Name = "0702", Text = "07 Detail 02"}
};
// Create Objectives first
var id = 1;
foreach (var item in objectiveData.Where(i => i.Name.EndsWith("00")))
{
Objectives.Add(new Objective { ObjectiveId = id, Name = item.Name, Text = item.Text });
id++;
}
// Create ObjectiveDetails
id = 1;
foreach (var item in objectiveData.Where(i => !i.Name.EndsWith("00")))
{
var itemLocal = item;
var matchingObjective = Objectives.FirstOrDefault(o => o.Name.StartsWith(itemLocal.Name.Substring(0, 2)));
var objectiveDetail = new ObjectiveDetail
{
ObjectiveDetailId = id,
Text = item.Text,
ObjectiveId = matchingObjective != null ? matchingObjective.ObjectiveId : 0,
Objective = matchingObjective
};
if (matchingObjective != null)
{
matchingObjective.ObjectiveDetails.Add(objectiveDetail);
}
id++;
}
// At the end of this method you should have a list of Objectives, each with their ObjectiveDetails children
}
}
Output:
Hope this helps.