Delete XML node that contains a certain value - c#

Here is the xml structure.
I am trying to delete each Status node where State contains the word failed.
What is the best way to remove these?
<Stats>
<Status>
<Desc>something here</Desc>
<State>pending - ok</State>
</Status>
<Status>
<Desc>something here</Desc>
<State>failed</State>
</Status>
</Stats>

void Main()
{
XDocument xml = XDocument.Parse(#"<Stats>
<Status>
<Desc>something here</Desc>
<State>pending - ok</State>
</Status>
<Status>
<Desc>something here</Desc>
<State>failed</State>
</Status>
</Stats>");
xml.Descendants("State").Where (x => x.Value.Contains("fail")).Ancestors("Status").Remove();
Console.WriteLine(xml.ToString());
}
Parse will load the xml in-memory, Load is used for loading it from a stream or via I/O means.
#Gregory Pilar's answer heavily influenced this answer; I believe he wrote that from memory, the snippet I provided was testing via LinqPad and returns expected results.

You can use Linq to XMl, to do the job
var xdoc = XDocument.Load(path_to_xml);
xdoc.Descendants("Status")
.Where(os => (int)os.Attribute("State") == "failed")
.Remove();
xdoc.Save(path_to_xml);

xDoc.Descendants("Status").Where(status => status.Element("State").Value.ToLower().Contains("fail")).Remove();

Related

Loading Descendants of XML using XDocument.Load

I am attempting to use XDocument.Load to access some latitude and longitude figures. Here is the example XML document;
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1">
<Copyright>Copyright © 2016 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.</Copyright>
<BrandLogoUri>http://dev.virtualearth.net/Branding/logo_powered_by.png</BrandLogoUri>
<StatusCode>200</StatusCode>
<StatusDescription>OK</StatusDescription>
<AuthenticationResultCode>ValidCredentials</AuthenticationResultCode>
<TraceId>31a206847f9341d28689e0e7185e163d|DB40051719|7.7.0.0|DB4SCH010061262</TraceId>
<ResourceSets>
<ResourceSet>
<EstimatedTotal>1</EstimatedTotal>
<Resources>
<Location>
<Name>SW1A 1AA, London, London, United Kingdom</Name>
<Point>
<Latitude>51.501018524169922</Latitude>
<Longitude>-0.14159967005252838</Longitude>
</Point>
<BoundingBox>
<SouthLatitude>51.497155806599245</SouthLatitude>
<WestLongitude>-0.14987251765942367</WestLongitude>
<NorthLatitude>51.5048812417406</NorthLatitude>
<EastLongitude>-0.1333268224456331</EastLongitude>
</BoundingBox>
<EntityType>Postcode1</EntityType>
<Address>
<AdminDistrict>England</AdminDistrict>
<AdminDistrict2>London</AdminDistrict2>
<CountryRegion>United Kingdom</CountryRegion>
<FormattedAddress>SW1A 1AA, London, London, United Kingdom</FormattedAddress>
<Locality>London</Locality>
<PostalCode>SW1A 1AA</PostalCode>
</Address>
<Confidence>High</Confidence>
<MatchCode>Good</MatchCode>
<GeocodePoint>
<Latitude>51.501018524169922</Latitude>
<Longitude>-0.14159967005252838</Longitude>
<CalculationMethod>Rooftop</CalculationMethod>
<UsageType>Display</UsageType>
</GeocodePoint>
</Location>
</Resources>
</ResourceSet>
</ResourceSets>
</Response>
And here is the code I am using attempting to access latitude and longitude;
string latitude = XDocument.Load(#"test.xml").Root
.Descendants("ResourceSets")
.Descendants("ResourceSet")
.Descendants("Resources")
.Descendants("Location")
.Descendants("GeocodePoint")
.Select(element => element.Attribute("Latitude").Value).FirstOrDefault();
But this returns an empty string. How can I navigate the document correctly?
First thing you don't need to call in multiples level Descendants method if you want to get all GeocodePoint nodes. You can only do this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";
string latitude = XDocument.Load(#"test.xml")
.Descendants(ns+"GeocodePoint")
.Select(e=> (string)e.Element(ns+"Latitude"))
.FirstOrDefault();
With that call Linq to XML will retrieve all the GeocodePoints in your xml
If you want to get lat and long values, then you can project either to an anonymous type or a custom class (DTO) like this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";
var coord= XDocument.Load(#"xml.xml")
.Descendants(ns+"GeocodePoint").Select(e => new { Lat = (string)e.Element(ns+"Latitude"), Lng = (string)e.Element(ns+"Longitude") })
.FirstOrDefault();
About your issue
Your problem was you were calling Attribute method to get the Latitude value, but as you can see in your xml structure GeocodePoint node doesn't have that as an attribute, it is a nested element. That's way you need to use Element method instead. The second issue was you need to take the namespace into account as I show above.
You are not using namespace. Your Xml provided with namespace
<Response xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1">
So you need to use it when searching for elements.
XDocument doc = XDocument.Load(#"C:\tmp\data.xml");
XNamespace ns = doc.Root.Name.Namespace;
string value = doc.Root.Descendants(ns + "Latitude").FirstOrDefault().Value;
Or search without namespace, by LocalName of element
string value = doc.Root
.Descendants
.Where(element => element.Name.LocalName.Equals("Latitude"))
.FirstOrDefault()
.Value;
If you using Descendats method, then you can search straight for element you need.

XML: Retrieve a particular value from xml

From the following XML, I want to find a value based on the Employer.
<?xml version="1.0" encoding="UTF-8"?>
<Document>
<Details>
<Employer>Taxes</Employer>
<Adr>
<Strt>Street</Strt>
<Twn>Town</Twn>
</Adr>
</Details>
<DetailsAcct>
<Recd>
<Payroll>
<Id>9</Id>
</Payroll>
</Recd>
<br>
<xy>A</xy>
</br>
</DetailsAcct>
</Document>
the C# code I applied is
detail = root.SelectSingleNode($"//w:Document//w:Employer[contains(text(), 'Taxes']/ancestor::Employer",nsmgr);
But it gives me an invalid token error.
What am I missing?
The error was due to [contains(...], notice closing parentheses is missing. And since you want to return Employer element, no need for ancestor::Employer here :
//w:Document//w:Employer[contains(., 'Taxes')]
If the XML posted resembles structure of the actual XML (except the namespaces), better to use more specific XPath i.e avoid using costly // :
/w:Document/w:Details/w:Employer[contains(., 'Taxes')]
An alternative is to use LINQ to XML.
If the XML is in a string:
string xml = "<xml goes here>";
XDocument document = XDocument.Parse(xml);
XElement element = document.Descendants("Employer").First();
string value = element.Value;
If the XML is in a .xml file:
XDocument document = XDocument.Load("xmlfile.xml");
XElement element = document.Descendants("Employer").First();
string value = element.Value;
You can also find an employer element with a specific value, if that's what you need:
XElement element = document.Descendants("Employer").First(e => e.Value == "Taxes");
Note: this will throw an exception if no element is found with the specified value. If that is not acceptable, then you can replace .First(...) with .FirstOrDefault(...) which will simply return null if no element is found.

How do I put XML into a List of Dictionaries with C# on Windows Phone 7

Here is the XML I have in a file:
SPECIAL NOTE:
This is a question for Windows Phone 7, not general C#
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<item>
<date>01/01</date>
<word>aberrant</word>
<def>straying from the right or normal way</def>
</item>
<item>
<date>01/02</date>
<word>Zeitgeist</word>
<def>the spirit of the time.</def>
</item>
</rss>
I need it in a List (aka array) of Dictionary objects. Each Dictionary represents an <item>. Each element like <word> is the key with type string and each value like "Zeitgeist" is the value with type string.
Is there any easy way to do this? I'm coming from Objective-C and iOS so this is completely new to me with .NET and C#.
LINQ-to-XML makes it pretty easy. Here's a complete example:
public static void Main(string[] args)
{
string xml = #"
<rss version='2.0'>
<item>
<date>01/01</date>
<word>aberrant</word>
<def>straying from the right or normal way</def>
</item>
<item>
<date>01/02</date>
<word>Zeitgeist</word>
<def>the spirit of the time.</def>
</item>
</rss>";
var xdoc = XDocument.Parse(xml);
var result = xdoc.Root.Elements("item")
.Select(itemElem => itemElem.Elements().ToDictionary(e => e.Name.LocalName, e => e.Value))
.ToList();
}
Instead of loading from a string with XDocument.Parse(), you would probably do XDocument.Load(filename) but either way you get an XDocument object to work with (I did a string just for example).
You can use Linq-Xml to do this:
var doc = XDocument.Parse(xml); //xml is a String with your XML in it.
doc
.Root //Elements under the root element.
.Elements("item") //Select the elements called "item".
.Select( //Projecting each item element to something new.
item => //Selecting each element in the item.
item //And creating a new dictionary using the element name
.Elements() // as the key and element value as the value.
.ToDictionary(xe => xe.Name.LocalName, xe => xe.Value))
.ToList();
Yes, there is an easy way, it's called LINQ to XML.
Some resources:
Parsing complex XML with C#
LINQ to read XML
http://msdn.microsoft.com/en-us/library/bb387098.aspx
Hope this helps...

Search XDocument using LINQ without knowing the namespace

Is there a way to search an XDocument without knowing the namespace? I have a process that logs all SOAP requests and encrypts the sensitive data. I want to find any elements based on name. Something like, give me all elements where the name is CreditCard. I don't care what the namespace is.
My problem seems to be with LINQ and requiring a xml namespace.
I have other processes that retrieve values from XML, but I know the namespace for these other process.
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
XNamespace xNamespace = "http://CompanyName.AppName.Service.Contracts";
var elements = xDocument.Root
.DescendantsAndSelf()
.Elements()
.Where(d => d.Name == xNamespace + "CreditCardNumber");
I really want to have the ability to search xml without knowing about namespaces, something like this:
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
var elements = xDocument.Root
.DescendantsAndSelf()
.Elements()
.Where(d => d.Name == "CreditCardNumber")
This will not work because I don't know the namespace beforehand at compile time.
How can this be done?
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Request xmlns="http://CompanyName.AppName.Service.ContractA">
<Person>
<CreditCardNumber>83838</CreditCardNumber>
<FirstName>Tom</FirstName>
<LastName>Jackson</LastName>
</Person>
<Person>
<CreditCardNumber>789875</CreditCardNumber>
<FirstName>Chris</FirstName>
<LastName>Smith</LastName>
</Person>
...
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Request xmlns="http://CompanyName.AppName.Service.ContractsB">
<Transaction>
<CreditCardNumber>83838</CreditCardNumber>
<TransactionID>64588</FirstName>
</Transaction>
...
As Adam precises in the comment, XName are convertible to a string, but that string requires the namespace when there is one. That's why the comparison of .Name to a string fails, or why you can't pass "Person" as a parameter to the XLinq Method to filter on their name.
XName consists of a prefix (the Namespace) and a LocalName. The local name is what you want to query on if you are ignoring namespaces.
Thank you Adam :)
You can't put the Name of the node as a parameter of the .Descendants() method, but you can query that way :
var doc= XElement.Parse(
#"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Request xmlns=""http://CompanyName.AppName.Service.ContractA"">
<Person>
<CreditCardNumber>83838</CreditCardNumber>
<FirstName>Tom</FirstName>
<LastName>Jackson</LastName>
</Person>
<Person>
<CreditCardNumber>789875</CreditCardNumber>
<FirstName>Chris</FirstName>
<LastName>Smith</LastName>
</Person>
</Request>
</s:Body>
</s:Envelope>");
EDIT : bad copy/past from my test :)
var persons = from p in doc.Descendants()
where p.Name.LocalName == "Person"
select p;
foreach (var p in persons)
{
Console.WriteLine(p);
}
That works for me...
You could take the namespace from the root-element:
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
var ns = xDocument.Root.Name.Namespace;
Now you can get all desired elements easily using the plus-operator:
root.Elements(ns + "CreditCardNumber")
I think I found what I was looking for. You can see in the following code I do the evaluation Element.Name.LocalName == "CreditCardNumber". This seemed to work in my tests. I'm not sure if it's a best practice, but I'm going to use it.
XDocument xDocument = XDocument.Load(#"C:\temp\Packet.xml");
var elements = xDocument.Root.DescendantsAndSelf().Elements().Where(d => d.Name.LocalName == "CreditCardNumber");
Now I have elements where I can encrypt the values.
If anyone has a better solution, please provide it. Thanks.
There's a couple answers with extension methods that have been deleted. Not sure why. Here's my version that works for my needs.
public static class XElementExtensions
{
public static XElement ElementByLocalName(this XElement element, string localName)
{
return element.Descendants().FirstOrDefault(e => e.Name.LocalName == localName && !e.IsEmpty);
}
}
The IsEmpty is to filter out nodes with x:nil="true"
There may be additional subtleties - so use with caution.
If your XML documents always defines the namespace in the same node (Request node in the two examples given), you can determine it by making a query and seeing what namespace the result has:
XDocument xDoc = XDocument.Load("filename.xml");
//Initial query to get namespace:
var reqNodes = from el in xDoc.Root.Descendants()
where el.Name.LocalName == "Request"
select el;
foreach(var reqNode in reqNodes)
{
XNamespace xns = reqNode.Name.Namespace;
//Queries making use of namespace:
var person = from el in reqNode.Elements(xns + "Person")
select el;
}
I a suffering from a major case of "I know that is the solution, but I am disappointed that that is the solution"... I recently wrote a query like the one below (which I will shortly replace, but it has educational value):
var result = xdoc.Descendants("{urn:schemas-microsoft-com:rowset}data")
.FirstOrDefault()?
.Descendants("{#RowsetSchema}row");
If I remove the namespaces from the XML, I can write the same query like this:
var result = xdoc.Descendants("data")
.FirstOrDefault()?
.Descendants("row");
I plan to write my own extension methods that should allow me to leave the namespaces alone and search for nodes like this:
var result = xdoc.Descendants("rs:data")
.FirstOrDefault()?
.Descendants("z:row");
//'rs:' {refers to urn:schemas-microsoft-com:rowset}
//'z:' {refers to xmlns:z=#RowsetSchema}
My comments just below the code point to how I would like to hide the ugliness of the solution in an Extension Methods library. Again, I'm aware of the solutions posted earlier - but I wish the API itself handled this more fluently. (See what I did there?)
Just use the Descendents method:
XDocument doc = XDocument.Load(filename);
String[] creditCards = (from creditCardNode in doc.Root.Descendents("CreditCardNumber")
select creditCardNode.Value).ToArray<string>();

extract data from rss feed in c#

I need to read the woeid from the xml doc below. I need to read and store the data into a string variable so I can query the yahoo weather service.
XML returned by query:
<query yahoo:count="1"
yahoo:created="2009-12-22T08:30:31Z"
yahoo:lang="en-US"
yahoo:updated="2009-12-22T08:30:31Z"
yahoo:uri="http://query.yahooapis.com/v1/yql?q=select+woeid+from+geo.places+where+text%3D%22farnborough%2C+greater+london%2Cuk%22">
−
<diagnostics>
<publiclyCallable>true</publiclyCallable>
−
<url execution-time="32">
http://where.yahooapis.com/v1/places.q(farnborough%2C%20greater%20london%2Cuk);start=0;count=10
</url>
<user-time>33</user-time>
<service-time>32</service-time>
<build-version>4265</build-version>
</diagnostics>
−
<results>
−
<place>
<woeid>19941</woeid>
</place>
</results>
</query>
Can someone show me how to do this through linq?
----------EDIT ------------------------------------------------------------------------------------------
I've just realised i linq is not supported by .net 2.0...doh
So please could some suggest an alternative way using references available with .net 2.0? -maybe repost and tag?
Many Thanks,
You can do it like this:
XDocument doc = XDocument.Parse(xml);
string s = doc.Descendants()
.Where(element => element.Name == "woeid")
.FirstOrDefault().Value;
You can use something similar to this Linq query to get the results back from the XML document
XDocument feeds = XDocument.Parse(xml);
var result = feeds.Descendants("diagnostics")
.Select(f => new
{
UserTime = f.Element("uset-time").Value,
ServiceTime = f.Element("service-time").Value,
//... etc
}.First();
Here is a way to extract values from an xml file using Linq and xPath.
Hope this helps some.

Categories