Reading Xml file contents to a class instance in C# - c#

i have a class like
class CXmlData
{
String Data1="";
String Data2="";
List<String> lst=new List<String>()
}
and a Dictionary
Dictionary<String,CXmlData> dict=new Dictionary<String,CXmlData>();
The xml file is having the following structure
<Root>
<ChildList>
<Children>
<Child Name="a1" val="A"/>
<Child Name="a2" val="A"/>
<Child Name="b1" val="B"/>
<Child Name="c1" val="C"/>
<Child Name="c2" val="C"/>
</Childen>
<Siblings_One>
<Sibling Name="A" Xpos="0" Ypos=""/>
<Sibling Name="B" Xpos="1" Ypos="1"/>
</Sibling_One>
<Siblings_Two>
<Sibling Name="C" Xpos="0" Ypos="0"/>
</Sibling_Two>
</ChildList>
</Root>
i need to read the data from the above xml file and to add to the dictionary
so the Resulting Dictionary will be like
Key(String) Value(Instance of CXmlData)
"A" Data1="0"
Data2="2"
lst=new List<String>{"a1","a2"}
"B" Data1="1"
Data2="1"
lst=new List<String>{"b1"}
"C" Data1="0"
Data2="2"
lst=new List<String>{"c1","c2"}
(if Xpos value is "0" then Data2(Ypos) value should be "2")
Now I'm using like
1.Read Sibling_One child values
if Xpos value is "0" the take Ypos value as"2"
else keep the same
2.Read Sibling_Two child values
if Xpos value is "0" the take Ypos value as"2"
else keep the same
3.Read Children->Child values
Compare Sibling->Name attribute ==Child->Name
if matches then add the Child->Name attribute value to the list
i'm using Xml Namespace(System.Xml) and foreach loop for this, but its taking more time to complete the process
EDIT Sample Code
XmlDocument XDoc = new XmlDocument();
XDoc.Load(Application.StartupPath + "\\foo.xml");
XmlElement XRoot = XDoc.DocumentElement;
List<string> TempList = new List<string>();
XmlNodeList XChildName = XDoc.SelectNodes("//ChildList/Sibling_One/Sibling");
foreach (XmlNode ch in XChildName)
{
TempList .Add(ch.Attributes["Name"].Value);
CXmlData P = new CXmlData();
if (ch.Attributes["Xpos"].Value == "0")
P.Data2 = "2";
else
P.Data2 = ch.Attributes["Ypos"].Value;
P.Data1 = ch.Attributes["Xpos"].Value;
dict.Add(ch.Attributes["NetName"].Value, P);
}
XChildName = XDoc.SelectNodes("//ChildList/Sibling_Two/Sibling");
foreach (XmlNode ch in XChildName)
{
TempList .Add(ch.Attributes["Name"].Value);
CXmlData P = new CXmlData();
if (ch.Attributes["Xpos"].Value == "0")
P.Data2 = "2";
else
P.Data2 = ch.Attributes["Ypos"].Value;
P.Data1 = ch.Attributes["Xpos"].Value;
dict.Add(ch.Attributes["Name"].Value, P);
}
foreach (string str in TempList )
{
List<String> lstChildValues = (from XmlNode xn in XDoc.SelectNodes("//ChildList/Children/Child")
where xn.Attributes["val"].Value == str
select xn.Attributes["Name"].Value).ToList();
if (dict.Keys.Contains(str))
{
CXmlData p = dict[str];
dict.Remove(str);
p.lst = lstChildValues;
dict.Add(str, p);
}
}
is there any way to do this using LINQ to Xml (System.xml.Linq)?

List<String>l_lstName = XDocument.Load("foo.xml")
.Descendants("Sibling")
.Select(Temp => Temp.Attribute("Name").Value).ToList();
Dictionary<String, CXmlData> dict = new Dictionary<string, CXmlData>();
dict = XDocument.Load("foo.xml")
.Descendants("Sibling").ToDictionary(
X => X.Attribute("Name").Value,
X => new CXmlData
{
lst =XDocument.Load("foo.xml").Descendants("Children").Descendants("Child").Where(Temp=>Temp.Attribute("val").Value==X.Attribute("Name").Value).Select(Temp=>Temp.Attribute("Name").Value).ToList(),
Data1 = X.Attribute("Xpos").Value == "0" ? "2" : X.Attribute("Ypos").Value,
Data2 = X.Attribute("Xpos").Value
}
);

Related

Searching for distinct XML structures in a document with Linq to XML in C#

I have written a little C# to parse through a number of elements in an XML document and return only the first of those elements which have a distinct child structure? For example, if I have the following XML doc, then a call to rootElement.DistinctStructures("base") let's say returns an IEnumerable<XElement> containing just the base elements with ids 1, 3, and 5.
<root>
<base id="1">
<a>text</a>
</base>
<base id="2">
<a>more text</a>
</base>
<base id="3">
<b>text</b>
</base>
<base id="4">
<a>other text</a>
</base>
<base id="5">
<a>
<c>sub text</c>
</a>
</base>
</root>
The basic code generates a unique key from the element name and text nodes in the structure and compares them to a saved collection of unique elements. My question is whether there is a neater way to do this?
private Dictionary<string, XElement> uniqueElements = new Dictionary<string, XElement>();
public void Go()
{
foreach (var entry in xmlDoc.Elements("e"))
{
string keyString = AsStructureString(entry).ToString();
if (!uniqueElements.Keys.Contains(keyString))
{
uniqueElements.Add(keyString, entry);
}
}
}
public StringBuilder AsStructureString(this XElement input)
{
StringBuilder sb = new StringBuilder(input.Name.LocalName);
var NodesOfNote = input.Nodes().Where(n => n.NodeType == XmlNodeType.Element || n.NodeType == XmlNodeType.Text).ToList();
if (NodesOfNote.Any())
{
sb.Append(">>");
}
foreach (var childNode in NodesOfNote)
{
if (childNode.NodeType == XmlNodeType.Element)
{
sb.Append((childNode as XElement).AsStructureString());
}
if (childNode.NodeType == XmlNodeType.Text)
{
sb.Append("txt");
}
if (!childNode.IsLastIn(NodesOfNote))
{
sb.Append("|");
}
}
return sb;
}
It might be easier than you think. If what determines the structure of a node is its elements and text (regardless of the content), you could do this:
IEnumerable<XElement> DistinctStructures(XContainer root, XName name)
{
return
from d in root.Descendants(name)
group d by GetKey(d) into g
select g.First();
string GetKey(XElement n) =>
String.Join(",",
n.DescendantNodes().Select(d =>
d is XElement e ? $"{e.Name}^{GetDepth(e)}"
: d is XText t ? $"<text>^{GetDepth(t)}"
: default
)
);
int GetDepth(XObject o)
{
var depth = 0;
for (var c = o; c != null; c = c.Parent)
++depth;
return depth;
}
}

How to check all child tags of a particular parent tags count?

this is a sample xml
<?xml version="1.0" encoding="utf-8"?>
<random>
<chkr id="1">
<ab>10.100.101.18</ab>
<xy>5060</xy>
<tt>pop</tt>
<qq>pop</qq>
</chkr>
<chkr id="2">
<ab>tarek</ab>
<tt>tarek</tt>
<ab>ffff</ab>
<foo>pop</foo>
</chkr>
<chkr id="3">
<ab>adf</ab>
<foo>adf</foo>
<tt>fadsf</tt>
<ab>fadsf</ab>
<tt>036</tt>
<foo>.3</foo>
<ssd>wolk</ssd>
</chkr>
</random>
I want to search for tags other than the tags <ab> and <tt> in each of the parent tags <chkr> and get the name of the tags that appear in that parent node more than once. i.e. in the above sample xml, the output should be <chkr id="3"> contains the tag <foo> multiple times.
How can I do this using LINQ-TO-XML?
Grouping by the name of all descendants is a very easy solution:
(x is the name of your XDocument)
foreach (var e in x.Descendants("chkr"))
{
foreach (var v in e.Descendants()
.Where(ee => ee.Name != "ab" && ee.Name != "tt")
.GroupBy(ee => ee.Name)
.Select(ee => new { Name = ee.Key, Count = ee.Count() }))
{
if (v.Count > 1)
Console.WriteLine($"<chkr id={e.Attribute("id").Value}> contains the tag <{v.Name}> {v.Count} times.");
}
}
With your XML this code would output
<chkr id=3> contains the tag <foo> 2 times.
EDIT: If you want the results as specified in you comment, just change your code to the following:
List<string> names = new List<string>();
List<int> counts = new List<int>();
foreach (var e in x.Descendants("chkr"))
{
names = new List<string>();
counts = new List<int>();
foreach (var v in e.Descendants().Where(ee => ee.Name != "ab" && ee.Name != "tt").GroupBy(ee => ee.Name).Select(ee => new { Name = ee.Key, Count = ee.Count() }))
{
if (v.Count > 1)
{
names.Add(v.Name.ToString());
counts.Add(v.Count);
}
}
if (names.Any())
Console.WriteLine($"<chkr id={e.Attribute("id").Value}> contains the tag/tags {String.Join(",", names)} {String.Join(",", counts)} times.");
}

Optimisation of LinqToXml

Could anyone tell me if this is correct or if there's a faster/cleaner way to do it?
//I load p as a xDoc so I can use linq to xml
XDocument myDoc = XDocument.Parse(p);
// retrieve each "Item" node except if orderedQuantity is 0 or name is "nullorderitem"
IEnumerable<XElement> orderedRes = from item in myDoc.Descendants("Item")
where ((double)item.Element("orderedQuantity") > 0 && item.Element("ResourceId").Name != "NULLOrderItem")
select item;
foreach (XElement xelem in orderedRes)
{
if(xelem.Element("Name").Value.ToLower() == "oneofmyvalueIneed"
|| xelem.Element("Name").Value.ToLower() == "anotherone"
|| xelem.Element("Name").Value.ToLower() == "yetanother")
{
FieldProperties elem = new FieldProperties();
elem.Fieldname = xelem.Element("Name").Value;
elem.Fieldvalue = xelem.Element("OrderedQuantity").Value;
lElem.Add(elem);
}
}
Knowing that lElem is a list of FieldProperties, and FieldProperties is a class looking like this :
FieldProperties
string fieldname
string fieldvalue
and p is a string that looks like
<items>
<item>
<resourceid>blablabla</resourceid>
<somerandomtag>whocares</somerandomtag>
<orderedquantity>0.0</orderedquantity>
</item>
<item>
<resourceid>oneofmyvalueIneed</resourceid>
<somerandomtag>whocares</somerandomtag>
<orderedquantity>1.0</orderedquantity>
</item>
<item>
<resourceid>yetanother</resourceid>
<somerandomtag>whocares</somerandomtag>
<orderedquantity>0.0</orderedquantity>
</item>
</items>
You can improve the if statement by using the .Contains method.
var validNames = new List<string> {"oneofmyvalueIneed","anotherone","yetanother"}
XDocument myDoc = XDocument.Parse(p);
var result = from item in myDoc.Descendants("Item")
where ((double)item.Element("orderedQuantity") > 0 &&
item.Element("ResourceId").Name != "NULLOrderItem") && // See Charles's comment about this line
validNames.Contains(iten.Element("Name").Value.ToLower())
select item;
foreach (var item in orderedRes)
{
FieldProperties elem = new FieldProperties();
elem.Fieldname = xelem.Element("Name").Value;
elem.Fieldvalue = xelem.Element("OrderedQuantity").Value;
lElem.Add(elem);
}
Then you can also replace the foreach with
select new FieldProperties
{
Fieldname = item.Element("Name").Value,
Fieldvalue = xelem.Element("OrderedQuantity").Value
};
Adding it all together + some optimization when accessing the child elements it would look like:
var validNames = new List<string> {"oneofmyvalueIneed","anotherone","yetanother"}
XDocument myDoc = XDocument.Parse(p);
var result = from item in myDoc.Descendants("Item")
let value = item.Element("orderedQuantity")
let name = iten.Element("Name").Value.ToLower()
where ((double)value > 0 &&
item.Element("ResourceId").Name != "NULLOrderItem") && // See Charles's comment about this line
validNames.Contains(name)
select new FieldProperties
{
Fieldname = name
Fieldvalue = value
};

I have a list which contains XML strings. How can I sort this list according to one of the XML attribute?

For example:
The list is defined as:
List<string> listOfXML = new List<string>();
If this list contains 3 XML strings as given below, how can I sort this list according to "ThirdAttribute"?
XML string 1:
<Element>
<FirstAttribute>A</FirstAttribute>>
<SecondAttribute>B</SecondAttribute>
<ThirdAttribute>3</ThirdAttribute>
</Element>
XML string 2:
<Element>
<FirstAttribute>C</FirstAttribute>>
<SecondAttribute>D</SecondAttribute>
<ThirdAttribute>4</ThirdAttribute>
</Element>
XML string 3:
<Element>
<FirstAttribute>A</FirstAttribute>>
<SecondAttribute>B</SecondAttribute>
<ThirdAttribute>1</ThirdAttribute>
</Element>
The sorted list should have the elements in following order:
<Element>
<FirstAttribute>A</FirstAttribute>>
<SecondAttribute>B</SecondAttribute>
<ThirdAttribute>1</ThirdAttribute>
</Element>
<Element>
<FirstAttribute>A</FirstAttribute>>
<SecondAttribute>B</SecondAttribute>
<ThirdAttribute>3</ThirdAttribute>
</Element>
<Element>
<FirstAttribute>C</FirstAttribute>>
<SecondAttribute>D</SecondAttribute>
<ThirdAttribute>4</ThirdAttribute>
</Element>
Using linq to xml you can do this:
orderedList = listOfXml.OrderBy(x =>(int)XElement.Parse(x).Element("ThirdAttribute"))
public class XmlAttributesIntComparer : IComparer<string>
{
private readonly string elementName;
public XmlAttributesIntComparer(string elementName)
{
this.elementName = elementName;
}
public int Compare(string x, string y)
{
XmlDocument doc1 = new XmlDocument();
doc1.LoadXml(x);
XmlDocument doc2 = new XmlDocument();
doc2.LoadXml(y);
XmlText text1 = (XmlText)doc1.DocumentElement[elementName].FirstChild;
XmlText text2 = (XmlText)doc2.DocumentElement[elementName].FirstChild;
int attr1 = Convert.ToInt32(text1.Value);
int attr2 = Convert.ToInt32(text2.Value);
return attr1.CompareTo(attr2);
}
}
// ...
listOfXML.Sort(new XmlAttributesIntComparer("ThirdAttribute"));
Denis has a better answer.
A very very very bad solution, but a solution nonetheless ...
List<string> listOfXML = new List<string>()
{
#"<Element>
<FirstAttribute>A</FirstAttribute>>
<SecondAttribute>B</SecondAttribute>
<ThirdAttribute>3</ThirdAttribute>
</Element>",
#"<Element>
<FirstAttribute>C</FirstAttribute>>
<SecondAttribute>D</SecondAttribute>
<ThirdAttribute>4</ThirdAttribute>
</Element>",
#"<Element>
<FirstAttribute>A</FirstAttribute>>
<SecondAttribute>B</SecondAttribute>
<ThirdAttribute>1</ThirdAttribute>
</Element>"
};
List<string> order = new List<string>();
int[] orders = new int[listOfXML.Count];
int arraylength = orders.Length;
for (int i = 0; i < arraylength; i++)
{
orders[i] = Convert.ToInt32(XElement.Parse(listOfXML[i]).Element("ThirdAttribute").Value);
}
Array.Sort(orders);
for (int i = 0; i < arraylength; i++)
{
string orderedXMLString = GetXmlString(orders[i], listOfXML);
order.Add(orderedXMLString);
}
private static string GetXmlString(int p, List<string> listOfXML)
{
string retXML = string.Empty;
foreach (var item in listOfXML)
{
if (p == Convert.ToInt32(XElement.Parse(item).Element("ThirdAttribute").Value))
{
retXML = item;
}
}
return retXML;
}
A better way would be to combine all the elements into one string.
Read that combined string as XML and sort using LINQ.
Add each element from the sorted XML to a list.
Another way is to try the let keyword in LINQ to read "ThirdAttribute" value and then somehow sort accordingly:
var orderedXML = from elem in listOfXML
let o = (XElement.Parse(elem).Element("ThirdAttribute").Value)
select new elem and orderby here;
I tried this initially but couldn't go beyond the let!!!
So came up with the bad solution above.

How to retrieve value from attribute in XML?

What I have:
<?xml version="1.0" encoding="Unicode" standalone="yes"?>
<FiberItems>
<Fiber China="1a" England="1b" Japan="1c" France="1d" Korea="1e"/>
<Fiber China="2a" England="2b" Japan="2c" France="2d" Korea="2e"/>
</FiberItems>
What I want:
1.retrive all the value of "China" into a string array.
2.if a value of China is "1a", retrive all the value of the rest attributes(1b,1c,1d,1e),.
What I do:
1. I write codes for purpose 1 , but never works >_<
XDocument doc = XDocument.Load("/FiberItems.xml");
IEnumerable<string> query = from fiber in doc.Root.Elements("Fiber")
select (string)fiber.Attribute("China");
string[] myString = new string[3];
for (int i = 0; i < 3; i++)
{
foreach (string item in query)
{
myString[i] = (string)item;
}
}
2. for purpose 2, have no idea yet >_<
need helps
You probably shouldn't use an array to collect your data but when you do:
string[] myString = new string[3];
// for (int i = 0; i < 3; i++)
// {
int i = 0;
foreach (string item in query)
{
myString[i] = (string)item;
i += 1;
}
// }
You are doing 3x3 rounds where only 3 are needed.
You can use the following code:
XDocument root = XDocument.Load("/FiberItems.xml");
var attributesChina = root.Elements("FiberItems").Elements("Fiber").Attributes("China");
// attributesChina will contain all the values of china
foreach (XAttribute china in attributesChina)
{
string value = china.value;
}
Check out System.Xml.Linq.
Here's an example to get a list of all the attributes for one element.
var xml = #"<?xml version=""1.0"" encoding=""Unicode"" standalone=""yes""?>
<FiberItems>
<Fiber China=""1a"" England=""1b"" Japan=""1c"" France=""1d"" Korea=""1e""/>
<Fiber China=""2a"" England=""2b"" Japan=""2c"" France=""2d"" Korea=""2e""/>
</FiberItems>";
XDocument doc = XDocument.Parse(xml);
XElement ele = doc.Root.Element("Fiber");
var att = ele.Attributes();
//Load XML element
XElement root = XElement.Parse(File.ReadAllText("/FiberItems.xml"));
//All china attributes (retrieves 1a,2a)
var chinaAttributes= root.Elements().Attributes("China");
//load all rest attributes for china = 1a, loads 1b,1c,1d,1e
var chinaOneARestAttributes = root.Elements().Where(a=>a.Attribute("China")!=null && a.Attribute("China").Value=="1a").Attributes().Select(x=>x.Value).Where(x=>!String.Equals(x,"1a"));
UPDATED for Null Reference Exception. With the data i had tried earlier, i ensured Attribute China was present for all elements.
In XPath:
/FiberItems/Fiber[#China='1a']/#*
gets you all the attributes of Fiber elements with China='1a' and
/FiberItems/Fiber[#China='1a']/#*[local-name()!='China']
gets you the same sequence of attributes excluding the China attribute.
So your requirement is if the attribute China value is "1a" then get all the attribute value of that node. I think it should work
XDocument doc = XDocument.Load("/FiberItems.xml");
IEnumerable<string> query = from fiber in doc.Root.Elements("Fiber")
//condition 1
where (string)fiber.Attribute("China") == "1a"
//condition 2 : Select all except china attribute
select fiber.Attributes().Where(a => a.Name !="China");
Using LINQ to XML:
var xmlStr = #"<?xml version=""1.0"" encoding=""Unicode"" standalone=""yes""?>
<FiberItems>
<Fiber China=""1a"" England=""1b"" Japan=""1c"" France=""1d"" Korea=""1e""/>
<Fiber China=""2a"" England=""2b"" Japan=""2c"" France=""2d"" Korea=""2e""/>
</FiberItems>";
var doc = XDocument.Parse(xmlStr);
var query =
from fiber in doc.Descendants("Fiber")
where (string)fiber.Attribute("China") == "1a"
select String.Join(", ",
(from attr in fiber.Attributes()
where attr.Name != "China"
select (string)attr).ToArray());
This will return a sequence of the other attribute values for each Fiber element that contains a China attribute with the value 1a.

Categories