Optimisation of LinqToXml - c#

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
};

Related

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.");
}

How to iterate XML by using XDocument in .Net

I have a big XML file where I am taking small snippet by using ReadFrom() and then I will get xmlsnippet which contains leaf, sas, kir tags at different positions (sometimes leaf at top compare to kir or viceversa).
Now the thing is I am using three foreach loop to get these values which is bad logic and it will take time when this snippet also big.
Is there anyway I can use one foreach loop and then three if loop inside foreach to get values?
arr is a custom arraylist
var xdoc = new XDocument(xmlsnippet);
string xml = RemoveAllNamespaces(xdoc.ToString());
foreach (XElement element in XDocument.Parse(xml).Descendants("leaf"))
{
arr.Add(new Test("leaf", element.Value, 2));
break;
}
foreach (XElement element in XDocument.Parse(xml).Descendants("sas"))
{
arr.Add(new Test("sas", element.Value, 2));
break;
}
foreach (XElement element in XDocument.Parse(xml).Descendants("kir"))
{
if (element.Value == "0")
arr.Add(new Test("kir", "90", 2));
break;
}
You only need to Parse that xmlsnippet once (assuming it fits in memory) and then use XNamespace to qualify the right XElement. No need to call RemoveAllnamespaces which I guess does what its name implies and probably does so in an awful way.
I used the following XML snippet as example input, notice the namespaces a, b and c:
var xmlsnippet = #"<root xmlns:a=""https://a.example.com""
xmlns:b=""https://b.example.com""
xmlns:c=""https://c.example.com"">
<child>
<a:leaf>42</a:leaf>
<a:leaf>43</a:leaf>
<a:leaf>44</a:leaf>
<somenode>
<b:sas>4242</b:sas>
<b:sas>4343</b:sas>
</somenode>
<other>
<c:kir>80292</c:kir>
<c:kir>0</c:kir>
</other>
</child>
</root>";
And then use Linq to either return an instance if your Test class or null if no element can be found. That Test class instance is then added to the arraylist.
var arr = new ArrayList();
var xdoc = XDocument.Parse(xmlsnippet);
// add namespaces
var nsa = (XNamespace) "https://a.example.com";
var nsb = (XNamespace) "https://b.example.com";
var nsc = (XNamespace) "https://c.example.com";
var leaf = xdoc.Descendants(nsa + "leaf").
Select(elem => new Test("leaf", elem.Value, 2)).FirstOrDefault();
if (leaf != null) {
arr.Add(leaf);
}
var sas = xdoc.Descendants(nsb + "sas").
Select(elem => new Test("sas", elem.Value, 2)).FirstOrDefault();
if (sas != null) {
arr.Add(sas);
}
var kir = xdoc.
Descendants(nsc + "kir").
Where(ele => ele.Value == "0").
Select(elem => new Test("kir", "90", 2)).
FirstOrDefault();
if (kir != null) {
arr.Add(kir);
}
I expect this to be the most efficient way to find those nodes if you want to stick with using XDocument. If the xml is really huge you might consider using an XMLReader but that probably only helps if memory is a problem.
If you want to do it one LINQ Query you can do this:
var q = xdoc
.Descendants()
.Where(elem => elem.Name.LocalName == "leaf" ||
elem.Name.LocalName == "sas" ||
elem.Name.LocalName == "kir" && elem.Value == "0" )
.GroupBy(k=> k.Name.LocalName)
.Select(k=>
new Test(
k.Key,
k.Key != "kir"? k.FirstOrDefault().Value: "90",
2)
);
arr.AddRange(q.ToList());
That query goes looking for all elements named leaf, sas or kir, groups them on the elementname and then takes the first element in each group. Notice the extra handling in case the elementname is kir. Both the where clause and the projection in Select need to deal with that. You might want to performance test this as I'm not sure how efficient this will be.
For completeness here is an XmlReader version:
var state = FoundElement.NONE;
using(var xe = XmlReader.Create(new StringReader(xmlsnippet)))
while (xe.Read())
{
// if we have not yet found an specific element
if (((state & FoundElement.Leaf) != FoundElement.Leaf) &&
xe.LocalName == "leaf")
{
// add it ... do not change the order of those arguments
arr.Add(new Test(xe.LocalName, xe.ReadElementContentAsString(), 2));
// keep track what we already handled.
state = state | FoundElement.Leaf;
}
if (((state & FoundElement.Sas) != FoundElement.Sas) &&
xe.LocalName == "sas")
{
arr.Add(new Test(xe.LocalName, xe.ReadElementContentAsString(), 2));
state = state | FoundElement.Sas;
}
if (((state & FoundElement.Kir) != FoundElement.Kir) &&
xe.LocalName == "kir")
{
var localName = xe.LocalName; // we need this ...
var cnt = xe.ReadElementContentAsString(); // ... because this moves the reader
if (cnt == "0") {
arr.Add(new Test(localName, "90", 2));
state = state | FoundElement.Kir;
}
}
}
And here is the enum with the different states.
[Flags]
enum FoundElement
{
NONE =0,
Leaf = 1,
Sas = 2,
Kir = 4
}

Grabbing Attribute from XMl document using Xdoc

Having a hard time retrieving an attribute from my XML. I need to grab this attribute, and send and store it. I can't get it to garb the attributes. :( Just need help with the attributes.
<portfolios>
<portfolio>
<id>00001</id>
<investment ticker="ASD">
<shares>20</shares>
<price>42.50</price>
</investment>
</portfolio>
<pricedata days="4">
<stock ticker="ASD">
<price value="42.50"/>
<price value="43.50"/>
<price value="39.00"/>
<price value="45.00"/>
</stock>
</pricedata>
</portfolios>
What I have so far!
public bool readXmlData(String filename)
{
XDocument document = XDocument.Load(filename);
foreach (XElement portfolio in document.Descendants("portfolio"))
{
XElement id = portfolio.Element("id");
string id2 = id != null ? id.Value : string.Empty;
portList.Add(new SmallPortfolio(id2));
XAttribute ticker = portfolio.Attribute("investment");
foreach(XElement investment in document.Descendants("investment"))
{
XElement shares = investment.Element("shares");
XElement price = investment.Element("price");
temp.Add(new Investment(
ticker != null ? ticker.Value : string.Empty,
shares != null ? int.Parse(shares.Value) : default(int),
price != null ? double.Parse(shares.Value) : default(double)
));
}
}
foreach (XElement stock in document.Descendants("pricedata"))
{
XAttribute tick = stock.Attribute("stock");
List<Double> pricetemp2 = new List<Double>();
foreach (XElement price in document.Descendants("stock"))
{
XAttribute value = price.Attribute("price");
pricetemp2.Add(value.Value);
}
groupList.Add(new PriceGroup(tick,pricetemp2));
}
return true;
}
public List<SmallPortfolio> getPortfolioList() { return null; }
public List<PriceGroup> getPriceList() { return null; }
}
<price> is an element, but you are accessing it as if it was an attribute <stock price="..."/>.
Try this:
foreach (XElement stock in document.Descendants("stock"))
{
string ticker = (string)stock.Attribute("ticker");
List<Double> pricetemp2 = new List<Double>();
foreach (XElement price in stock.Descendants("price"))
{
double value = (double)price.Attribute("value");
pricetemp2.Add(value);
}
groupList.Add(new PriceGroup(ticker, pricetemp2));
}
Casting XAttribute to double will use the proper XML rules for numbers (XmlConvert.ToDouble). Using double.Parse is incorrect as it uses culture-specific number formatting (e.g. in Germany it expects a decimal comma instead of a decimal point).

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.

Reading Xml file contents to a class instance in 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
}
);

Categories