I am preferring LINQ to XML over XMLReader because it feels much easier to use. However, I know I'm doing it wrong somewhere. I'm not looking for faster execution or anything, just a cleaner rewrite. I can't tell if it'll fit cleanly into the from foo in bar where foo is some condition form, but there's got to be a cleaner way.
I am not using anonymous objects here and instead outputting as strings because I am excluding some code which will adapt the data into existing objects - however, it is kind of a messy structure currently which I need to refactor.
My XML looks like this.
<?xml version="1.0" encoding="utf-8" ?>
<entity name="Something">
<Component type="RenderComponent">
<Material type="string">fur</Material>
<Reflectiveness type="int">678</Reflectiveness>
</Component>
<Component type="HealthComponent">
<Sound type="int">60</Sound>
</Component>
</entity>
And my code:
static void Main(string[] args)
{
XDocument xdoc = XDocument.Load(#"..\..\XMLFile1.xml");
List<string> comNames = new List<string>();
Dictionary<string, string> paramValues = new Dictionary<string, string>();
List<string> paramTypes = new List<string>();
foreach (XElement e in xdoc.Root.Elements("Component"))
{
string type = e.Attribute("type").Value;
foreach(XElement param in e.Elements())
{
paramValues.Add(param.Name.LocalName, param.Value);
paramTypes.Add(param.Attributes().First().Value);
}
Console.WriteLine(" \n\n\n");
comNames.Add(type);
}
}
Giving outputs of:
comNames - RenderComponent, HealthComponent
paramValues - (Material, fur), (Reflectiveness, 678), (Sound, 60)
paramTypes - string, int, int
If it clears it up somewhat, the general layout of the file:
Root node entity with a name attribute (forgot in this example)
n Component nodes with type attributes
Each Component node has a number of child nodes which have names, a type attribute, and a value.
I think, you can do something like this:
XDocument xdoc = XDocument.Load(#"..\..\XMLFile1.xml");
var data =
xdoc.Root.Elements("Component")
.SelectMany(e => new
{
type = e.Attribute("type").Value,
paramValues = e.Elements()
.Select(x => new KeyValuePair<string,
string>
(x.Name.LocalName,
x.Value)),
paramType = e.Elements()
.Select(x => x.Attributes().First()
.Value)
});
List<string> comNames = data.Select(x => x.type).ToList();
List<string> paramTypes = data.Select(x => x.paramType).ToList();
Dictionary<string, string> paramValues = data.Select(x => x.paramValues)
.ToDictionary(x => x.Key,
x => x.Value);
But honestly, I think your original code is just fine.
Related
So I'm working with reading an xml file to create a dictionary, but I can't figure out how to access the xml fields I want.
Below is the format of the XML I want to read.
<Days>
<Day Name="Monday">
<Task Order="1">TestTask</Task>
<Task Order="2">Test2</Task>
</Day>
</Days>
Below is my code so far. I've tried a lot of variations for finding task and order, such as for task: (string)e, or e.ToString(), or e.Elements("Task").Value.ToString(); And for order e.Attributes("Order").ToString();
string today = DateTime.Now.ToString("dddd");
var allItems = new Dictionary<string, int>();
XElement root = XElement.Parse(_orderxml);
IEnumerable<XElement> address =
from el in root.Elements("Day")
where el.Attribute("Name").Value == today
select el;
foreach (XElement e in address)
{
string task = ???;
string order = ???;
allItems.Add(task, (int)order);
}
So far, none of these have given me the right results, and I'm really unsure of what the proper way to get this data is, so any help would be appreciated!
Add a second loop to iterate the tasks and extract the values
static void Main()
{
string _orderxml = #"<Days> <Day Name=""Wednesday""> <Task Order=""1"">TestTask</Task> <Task Order=""2"">Test2</Task> </Day></Days>";
string today = DateTime.Now.ToString("dddd");
var allItems = new Dictionary<string, int>();
XElement root = XElement.Parse(_orderxml);
IEnumerable<XElement> address =
from el in root.Elements("Day")
where el.Attribute("Name").Value == today
select el;
foreach (XElement e in address)
{
foreach (XElement t in e.Descendants())
{
string task = t.Value.ToString();
int order = int.Parse(t.Attribute("Order").Value.ToString());
allItems.Add(task, (int)order);
}
}
}
Or you can do it with a Linq query like this
var result=root.Descendants("Day").Where(d=>d.Attribute("Name").Value==today).Descendants("Task").Select(x => new {Task=x.Value,Order=x.Attribute("Order") });
Or create a dictionary from the anonymous objects
var result = root.Descendants("Day").Where(d=>d.Attribute("Name").Value==today).Select(x => new { Task = x.Value.ToString(), Order = x.Attribute("Order") }).ToDictionary(c => c.Task, c => c.Order);
Or create a dictionary directly from the linq query
var result = root.Descendants("Day").Where(d=>d.Attribute("Name").Value==today).ToDictionary(c => c.Value.ToString(), c => int.Parse(c.Attribute("Order").Value.ToString()));
suppose I have the following XML string:
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item1>value1</item1>
<item2>value2</item2>
<item3>value3</item3>
<item4>value4</item4>
<item5>value5</item5>
<item6>value6</item6>
</items>
I need to parse it in a generic way as it may be updated later, and I don't need to modify my code accordingly.
So I have tried the following:
public static Dictionary<string, string> Parser(string xmlString)
{
Dictionary<string, string> parserDictionary = new Dictionary<string, string>();
using (StringReader stringReader = new StringReader(xmlString))
using (XmlTextReader reader = new XmlTextReader(stringReader))
{
// Parse the file and display each of the nodes.
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
parserDictionary.Add(reader.Name, reader.ReadString());
break;
}
}
}
return parserDictionary;
}
This code have 2 issues:
It parse the <items> element with null value, I don't need to
parse it
it ignores <item1>
please advise
Why not something like this:
var parserDictionary = XDocument.Create(xmlString)
.Descendants("items")
.Elements()
.Select(elem => new { Name = elem.Name.LocalName, Value = elem.Value })
.ToDictionary(k => k.Name, v => v.Value);
You could probably even do this:
var parserDictionary = XDocument.Create(xmlString)
.Descendants("items")
.Elements()
.ToDictionary(k => k.Name.LocalName, v => v.Value);
If you need to convert XML to an object representation than that's trivially easy
XDocument xDoc = XDocument.Parse(xmlString);
That's really all you need to do. Once you do that, you can query your xDoc with the Elements, Element, Attribute, Attributes and Descendants Properties.
For an example, here's some code that will print all of your values
XDocument xDoc = XDocument.Parse(xmlString);
foreach(XElement e in xDoc.Elements())
{
Console.WriteLine(e.Value);
}
So, I have the following function that takes in a Dictionary of Users and ControlNumbers and outputs it to XML. Found some LINQ online that did this very well; but I have one small problem.
static Dictionary<string, User> UserClassDict = new Dictionary<string, User>();
static void DictionaryToXML(Dictionary<string,User> UserClassDict)
{
XElement el = new XElement("root", UserClassDict.Select(kv => new XElement(kv.Key, kv.Value.ControlNumber
)));
}
The XML looks like this:
<root>
<adolan>792365</adolan>
<afeazell>791964</afeazell>
<amsmith>790848</amsmith>
<asnyder>790948789358</asnyder>
</root>
But as you can see, the ControlNumbers are generally 6 digits long (HOWEVER this is not always the case). What I would like to happen is something similar to this.
<root>
<adolan>
<controlNumbers>123456</controlNumbers>
</adolan>
<asnyder>
<controlNumbers>222111</controlNumbers>
<controlNumbers>333222</controlNumbers>
</asnyder>
</root>
Eventually I will have the program read this XML file at start up and populate the Dictionary so this XML will eventually get pretty large. Any ideas would be helpful.
Try this
XElement el = new XElement("root",
UserClassDict.Select(kv => new XElement(kv.Key,
kv.Value.ControlNumbers.Select(num => new XElement("controlNumbers", num))))
);
I don't fully understand how 2 or more control numbers are represented in your dictionary, but if you want to do some more complex xlm generation, you can change your lambda so that it invokes a method.
kv => new XElement(kv.Key, kv.Value.ControlNumber)
would change to
kv => BuildXMLElement(kv)
and you can implement BuildXMLElement to build the element as you like
Change your el to
XElement el = new XElement("root", UserClassDict.
Select(kv => new XElement(kv.Key,
from it in kv.Value.ControlNumber
select new XElement("controlNumbers", it)
)));
The above LINQ query will create multiple controlNumbers tags
To concatenate, use
XElement el = new XElement("root", UserClassDict.
Select(kv => new XElement(kv.Key,
String.Join(",", kv.Value.ControlNumber.ToArray())
)));
string PrintDict(Dictionary<string, string> MyDict)
{
XElement p = new XElement("DictionaryContents");
MyDict.ForEach(kvp => p.Add(new XElement(kvp.Key, kvp.Value)));
return p.ToString();
}
Is there a nicer way?
Reverse the way you're approaching the problem: don't write the dictionary to an XElement, but try to construct an XElement from a dictionary. LINQ-to-XML makes this particularly easy.
var xml = new XElement("DictionaryContents",
myDict.Select(kvp => new XElement(kvp.Key, kvp.Value)));
return xml.ToString();
I have a list of String
List<String> lst=new List<String>{"A","B","C"}
And an xml file like
<Root>
<ChildList>
<Childs>
<Child Name="a1" Val="A"/>
<Child Name="a2" val="A"/>
<Child Name="b1" val="B"/>
</Childs>
</ChildList>
</Root>
i need to read contets of the xml file and add to a dictionary
Dictionary<String,List<String>> dict
where the dictionary key is the items in the "lst" and value is the attribute value of "Name" from the file
So the result will be like
Key(String) Value(List<String>)
"A" "a1","a2"
"B" "b1"
"C" null
now i'm using nested for loop for this
Is there any wau to do this using LINQ to XML
Thanks in advance
I think this will do it:
XDocument doc = XDocument.Load("foo.xml");
ILookup<string, string> lookup = doc.Descendants("Childs")
.First()
.Elements("Child")
.ToLookup(x => (string) x.Attribute("Val"),
x => (string) x.Attribute("Name"));
var dictionary = lst.ToDictionary(x => x,
x => lookup[x].ToList().NullIfEmpty());
Using a helper method:
public static List<T> NullIfEmpty<T>(this List<T> list)
{
return list.Count == 0 ? null : list;
}
If you don't mind having an empty list instead of null for items which aren't in the XML file, the second statement can be simplified, with no need for the helper method:
var dictionary = lst.ToDictionary(x => x, x => lookup[x].ToList());
Note that I've structured this so that it only needs to go through the XML file once, instead of searching through the file once for each element in the list.
var xml = #"<Root>
<ChildList>
<Childs>
<Child Name=""a1"" Val=""A""/>
<Child Name=""a2"" Val=""A""/>
<Child Name=""b1"" Val=""B""/>
</Childs>
</ChildList>
</Root>";
var lst= new List<String> { "A", "B", "C" };
var doc = XDocument.Parse(xml);
var dict = (from item in lst
select new
{
Key = item,
Value = (from elem in doc.Root.Element("ChildList").Element("Childs").Elements("Child")
where (string)elem.Attribute("Val") == item
select (string)elem.Attribute("Name")).ToList()
}).ToDictionary(i => i.Key, i => i.Value);
This can be made more efficient. I iterate over the elements once for every item in lst. I'll properly come up with another solution later if others don't come up with one.