How to access element in XML files with attribute values? - c#

This is my XML file
<colleges>
<college college_name="DYPSOE">
<departments>
<department department_name="Computer" id="10">
<![CDATA[I NEED TO CHANGE THIS COMMENT!]]>
</department>
<department department_name="Machanical" id="20">
<![CDATA[I NEED TO CHANGE THIS COMMENT!]]>
</department>
</departments>
</college>
<college college_name="DYPSOET">
<departments>
<department department_name="Computer" id="10">
<![CDATA[I NEED TO CHANGE THIS COMMENT!]]>
</department>
<department department_name="Machanical" id="20">
<![CDATA[I NEED TO CHANGE THIS COMMENT!]]>
</department>
</departments>
</college>
</colleges>
I have three attribute values as college_name, department_name and id available in the program. So I want to go to the particular "Department" node and change the value in the comments with these three attribute values.
I'am trying to reach the node with different queries, failed so far.
var node = from e in doc.Descendants("college")
where e.Attribute("college_name").ToString() == college_name
select (XElement)e.Elements("department");
foreach (XElement data in node)
{
Console.WriteLine(data); //Just to look what I got
data.Value = ""; //To change the comment section
}
This is not working at all. If you guys could suggest me the query it would help me a lot.

Presuming you want to select a single department element based on college and department names, you can find it using a query like this one:
var query = from college in doc.Descendants("college")
where (string) college.Attribute("college_name") == "DYPSOE"
from department in college.Descendants("department")
where (string) department.Attribute("department_name") == "Computer"
select department;
var element = query.Single();
You can then replace the comment like this:
element.ReplaceNodes(new XCData("new comment"));

I think you can use System.Xml.XmlReader to read the nodes' attribute and write it with System.Xml.XmlWriter
How to: Parse XML with XmlReader has the example of parsing and writing.

Related

How to do operations (avg, cnt, etc) while parsing xml (c#)?

I have the following xml:
<bookstore>
<book IMDB="11-023-2022">
<title>Hamlet 2</title>
<comments>
<user rating="2">good enough</user>
<user rating="1">didnt read it</user>
<user rating="5">didnt read it but title is good</user>
</comments>
</book>
</bookstore>
I have an AverageUserRating property which i supposed to fill while parsing in the following format, I also have no idea how to cast comments into list. I tried everything, I can't use nuget packages like xpath. Thank you for your help.
return xdoc.Descendants("book").Select(n => new Books()
{
IMDB = n.Attribute("IMDB").Value,
Title = n.Element("title").Value,
//Comments = (List<string>)(n.Elements("user")), ???
//AverageUserRating= ???
}).ToList();
Comments = n.Element("comments").Elements("user").Select(u => u.Value).ToList(),
Explation:
1) Element("comments"), returns the child html element named "comments"
2) Elements("user"), returns all childrens elements named "user"
3) .Select(u => u.Value), select from every user element the value, that is the comment that you need
4) .ToList() converts into a list of strings
AverageUserRating = n.Element("comments").Elements("user").Select(u => u.Attribute("rating").Value).Select(r => Convert.ToInt32(r)).Average()
Explation:
1) Element("comments"), returns the child html element named "comments"
2) Elements("user"), returns all childrens elements named "user"
3) .Select(u => u.Attribute("rating").Value), selects from any element the value of the attribute "rating"
4) .Select(r => Convert.ToInt32(r)) converts the string value of the attribute into an int32 (pay attention, if the value is not a number, it throws an exception)
5) .Average() It calculates the aritmetic average and returns a double
Maybe, you should process original XML with XSLT to get the data you need automatically. Then, resulting doc could be easier to parse. Take a look here as an example Calculate average with xslt
It uses HTML as output format, you can do the same with XML.
Another option can be to create the classes with the same structure as your original XML so then you could employ automatic deserialization. Then, use LINQ or any other way to get the stats.

How to Select Multiple XML tags as XElement By attatribute value?

How to select Multiple XML Tags as XElement, filtering based on same attribute.
I have Below code i want to select tags with are having action=true
<root>
<first action="true">
<path>E:\Myfolder</path>
</first>
<second>
<path>C:\Users\</path>
</second>
<third action="true">
<name>Mytasks</name>
</third>
</root>
and Output shout be like this
<first action="true">
<path>E:\Myfolder</path>
</first>
<third action="true">
<name>Mytasks</name>
</third>
anybody please help me. I used FirstorDefault() But i am getting only one record among all
Try This .
$(path).find('root').find('[action="true"]')
Try This
xd = XDocument.Load("XML FILE PATH");
xe = xd.Root;
IEnumerable<XElement> oColl = from x in xe.Descendants() where ((string)x.Attribute("action")).equals("true") select x;

How to change an attribute in a subnode in C#?

I want to change the attribute value (in this case "51") of the "NextMemberId" in an xml file that looks like this:
<File>
<MemberList>
<NextMemberId Value="51" />
<Member Id="1" ..... />
<Member Id="2" ..... />
</MemberList>
</File>
The following code works, but I would like to know if it can be done in a more direct way without having to run a foreach loop:
var memberId = 1;
var memberlist = Doc.DocumentElement.SelectSingleNode("MemberList");
foreach (XmlNode node in memberlist.ChildNodes)
{
var nodeElement = node as XmlElement;
if (nodeElement != null && nodeElement.Name == "NextMemberId")
{
nodeElement.SetAttribute("Value", memberId.ToString());
}
}
Thanks for any inspiration!
The correct path to get NextMemberId from File according to your sample XML would be :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId");
nodeElement.SetAttribute("Value", memberId.ToString());
If there are multiple NextMemberId in your actual XML, and you need to filter by Value attribute, then you can add an XPath predicate similar to what the other answer suggested :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId[#Value=51");
Notice that you can choose to keep or leave single-quotes around 51 depending on whether you want compare the Value as a string or a number, respectively.
You can select single node with specified attribute like this:
var nextMemberIdNode = Doc.DocumentElement.SelectSingleNode("NextMemberId[#Value='51']")

Morelinq ExceptBy using several specific element

There are 2 xml files
First xml file contains:
<Prices>
<Price>
<SalesOrg>700</SalesOrg>
<AreaOfPricing>D20</AreaOfPricing>
<ProductId>20228090</ProductId>
<EffectiveDate>2015-05-11T00:00:00+7</EffectiveDate>
<DistributorPriceFibrate>200</DistributorPriceFibrate>
<CustomerPriceFibrate>20</CustomerPriceFibrate>
<CustomerPriceInDozen>30</CustomerPriceInDozen>
<CustomerPriceinPC>80.00</CustomerPriceinPC>
<CompanyID>001</CompanyID>
<ValidTo>2999-12-31T00:00:00+7</ValidTo>
<UOM>CS</UOM>
<Currency>IDR</Currency>
</Price>
<Price>
<SalesOrg>700</SalesOrg>
<AreaOfPricing>D20</AreaOfPricing>
<ProductId>20228090</ProductId>
<EffectiveDate>2015-05-11T00:00:00+7</EffectiveDate>
<DistributorPriceFibrate>200</DistributorPriceFibrate>
<CustomerPriceFibrate>20</CustomerPriceFibrate>
<CustomerPriceInDozen>30</CustomerPriceInDozen>
<CustomerPriceinPC>80.00</CustomerPriceinPC>
<CompanyID>001</CompanyID>
<ValidTo>2999-12-31T00:00:00+7</ValidTo>
<UOM>CS</UOM>
<Currency>IDR</Currency>
</Price>
<Price>
<SalesOrg>700</SalesOrg>
<AreaOfPricing>D20</AreaOfPricing>
<ProductId>20228090</ProductId>
<EffectiveDate>2015-05-11T00:00:00+7</EffectiveDate>
<DistributorPriceFibrate>180</DistributorPriceFibrate>
<CustomerPriceFibrate>20</CustomerPriceFibrate>
<CustomerPriceInDozen>30</CustomerPriceInDozen>
<CustomerPriceinPC>80.00</CustomerPriceinPC>
<CompanyID>001</CompanyID>
<ValidTo>2999-12-31T00:00:00+7</ValidTo>
<UOM>CS</UOM>
<Currency>IDR</Currency>
</Price>
</Prices>
and the second xml file:
<Prices>
<Price>
<SalesOrg>700</SalesOrg>
<AreaOfPricing>D20</AreaOfPricing>
<ProductId>20228090</ProductId>
<EffectiveDate>2015-05-11T00:00:00+7</EffectiveDate>
<DistributorPriceFibrate>200</DistributorPriceFibrate>
<CustomerPriceFibrate>20</CustomerPriceFibrate>
<CustomerPriceInDozen>30</CustomerPriceInDozen>
<CustomerPriceinPC>80.00</CustomerPriceinPC>
<CompanyID>001</CompanyID>
<ValidTo>2999-12-31T00:00:00+7</ValidTo>
<UOM>CS</UOM>
<Currency>IDR</Currency>
</Price>
</Prices>
What I want is, using morelinq features ExceptBy(), or using custom class extend IEqualityComparer on Except() features in Linq to return something like this (between 1st xml file and the 2nd xml file, even when the third tag price on 1st xml file have different DistributorPriceFibrate value):
<Prices/>
Since Except() compares all values on element 'Price' node, I just want compare only specific element at <ProductId> and <EffectiveDate> only.
If they are the same, then go empty tag <Prices/>. If not same value on those elements, return the price tag from 1st xml file which not have same value ProductID and EffectiveDate from 2nd xml file.
What I've done I distinct the 1st xml file:
var distinctItemsonxmldoc1 =
xmldoc1
.Descendants("Price")
.DistinctBy(element => new
{
ProductId = (string)element.Element("ProductId"),
EffectiveDate = (string)element.Element("EffectiveDate")
});
var afterdistinctxmldoc1 = new XElement("Prices");
foreach (var a in distinctItemsonxmldoc1 )
{
afterdistinctxmldoc1.Add(a);
}
and when using except to compare between 2 files:
var afterexcept = afterdistinctxmldoc1.Descendants("Price").Cast<XNode>().Except(xmldoc2.Descendants("Price").Cast<XNode>(), new XNodeEqualityComparer());
but it compare all element value on price node.
how using ExceptBy() in spesific element?
or custom IComparer maybe?
Thanks before.
EDIT
already solved. see the answer by #dbc.
To confirm I understand your question: given two XML documents, you want to enumerate through instances of each Price element in the first document with distinct values values for the child elements ProductId and EffectiveDate, skipping all those whose ProductId and EffectiveDate match a Price element in the second document, using MoreLinq.
In that case, you can do:
var diff = xmldoc1.Descendants("Price").ExceptBy(xmldoc2.Descendants("Price"),
e => new { ProductId = e.Elements("ProductId").Select(p => p.Value).FirstOrDefault(), EffectiveDate = e.Elements("EffectiveDate").Select(p => p.Value).FirstOrDefault() });

How to modify XML file in c#?

<Customers>
<Customer1>
<Name>Bobby</Name>
<Age>21</Age>
<Address>Panjim</Address>
</Customer1>
<Customer2>
<Name>Peter</Name>
<Age>32</Age>
<Address>Panjim</Address>
</Customer2>
<Customer4>
<Name>Joel</Name>
<Age>32</Age>
<Address>Mapusa</Address>
</Customer4>
</Customers>
So the thing is I want to delete a particular element and when i delete the first element i.e customer1, I want to update the other elements. I mean I want to make customer3, customer2 and customer2, customer1.
Can anyone please help me achieve this?
What about:
class Program {
static void Main(string[ ] args) {
XDocument doc = XDocument.Load("D:\\file.xml"); //example file
doc.Root.SwitchAndRemove("Customer1");
doc.Save("D:\\file.xml");
}
}
public static class Utilities {
public static void SwitchAndRemove(this XElement customers, XName name) {
var x = customers.Descendants().Where(e => e.Name == name).Select((element, index) => new { element, index }).Single();
int count = 0;
XElement temp = x.element;
foreach (XElement el in customers.Nodes()) {
if (count == x.index + 1) {
temp.RemoveAll();
temp.Add(el.Descendants().ToArray());
temp = el;
}
else
count++;
}
temp.Remove();
}
}
By giving as input your xml the output is the following:
<?xml version="1.0" encoding="utf-8"?>
<Customers>
<Customer1>
<Name>Peter</Name>
<Age>32</Age>
<Address>Panjim</Address>
</Customer1>
<Customer2>
<Name>Joel</Name>
<Age>32</Age>
<Address>Mapusa</Address>
</Customer2>
</Customers>
I'd argue that your problem is not how you could rename your nodes with minimum effort but structure of your XML file.
You said order of customers is not important and apparently customer tag's number is not important, either, since you want to rename the tags upon deletion.
So maybe this structure just creates unnecessary complexity and extra work for you.
Only reason I see you could need the number in tag is to identify the node you are about to remove. Am I right or is there something more to it? If not then you could add random unique identifier (like Guid) to your customer data to remove the right one.
Could save you lot of trouble.
<customers>
<customer>
<guid>07fb-877c-...</guid>
<name>Notch</name>
<age>34</age>
<address>street</address>
</customer>
<customer>
<guid>1435-435a-...</guid>
<name>Sam</name>
<age>23</age>
<address>other</address>
</customer>
<customers>
Say the element you have to delete is Customer1, first of all you can read the complete xml file using one of the XML parsing classes available in c# like XDocument or XmlReader and write to another xml file say "Temp.xml" skipping the Customer1 element completely. This way we have achieved the deletion part.
Next to update, forget the file being XML file and read the entire file to a string, say "xmlstring". Now use the Replace function available with a string data type to replace "Customer2" with "Customer1" and then "Customer3" with "Customer2" and so on.
And now delete your original XML file and write the string "xmlstring" using a stream writer to a file name "YourFileName.xml"
Thats it. Hope this solution works for you. Try this and in case u are unable get this done, share the code which u tried and we shall suggest how to work it out.
taken from your comment that the order does not have to be preserved then you can do this
public static void RemoveCustomer(XElement customers, XElement removeThis){
var last = customeers.Elements().Last();
if(last != removeThis){
foreach(var element in removeThis.Elements()){
element.Value = last.Element(element.Name).Value;
}
}
last.Remove();
}
It effectively substitutes the one to be removed with the last (unless the last should be removed) and thereby eliminates the need for renaming any of the other elements

Categories