LINQ to XML: How to get all elements by value - c#

I'm trying to get all elements with a given value, "John", from an xml document.
Is this possible with LINQ to XML?
What I want to achieve is to replace all "John" values with "Wayne". I know this can easily be done with xslt, but I need to do this by code.
My XML:
<Root>
<Parents>
<Parent>
<Name>John</Name>
<Age>18</Age>
</Parent>
<Parent>
<Name>John</Name>
<Age>25</Age>
</Parent>
<Parent>
<Name>Peter</Name>
<Age>31</Age>
</Parent>
</Parents>
</Root>
I have tried this:
XmlDocument doc = new XmlDocument();
doc.Load(#"C:/Temp/test.xml");
var elements = doc.Elements().Where(w => w.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}

You may use System.Xml.Linq.XDocument. It's more easy to work with.
XDocument doc = XDocument.Load(your file path);
var elements = doc.Descendants("Name").Where(i => i.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
doc.Save(your save file path);
Here is the output:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Parents>
<Parent>
<Name>Wayne</Name>
<Age>18</Age>
</Parent>
<Parent>
<Name>Wayne</Name>
<Age>25</Age>
</Parent>
<Parent>
<Name>Peter</Name>
<Age>31</Age>
</Parent>
</Parents>
</Root>

Here is an approach that will get all elements with the value John, regardless of what element (although only at the same level; you'd have to modify it to look at different levels too; you could use the Descendants approach described previously):
XDocument doc = XDocument.Load(#"C:\temp\test.xml");
var ns = doc.Root.GetDefaultNamespace();
var elements = doc.Element(ns + "Root").Element(ns + "Parents").Elements(ns + "Parent").Elements().Where(w => w.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
var stream = new FileStream(#"C:\temp\test.xml", FileMode.Create);
doc.Save(stream);

Related

XPath 1.0 select siblings with namespaces

I have the following xml file
<root xmlns="http://mynamespace">
<parent>
<first>text</first>
<second>more</second>
</parent>
<parent>
<first>2</first>
<second>3</second>
</parent>
<parent>
<first>aa</first>
<second>bb</second>
</parent>
</root>
I'm trying to get first and second children of parent.
C# seems to have problems with the following code (the error is on the last line):
var rawXml = #"<root xmlns=""http://mynamespace"">
<parent>
<first>text</first>
<second>more</second>
<third>hello</third>
</parent>
<parent>
<first>2</first>
<second>3</second>
<parent>
<first>a</first>
<second>b</second>
</parent>
</parent>
<parent>
<first>aa</first>
<second>bb</second>
</parent>
</root>";
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(rawXml);
var ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("m", "http://mynamespace");
var nav = xmlDoc.CreateNavigator();
var parents = nav.Select("//m:parent", ns);
Console.Write($"Got {parents.Count} parents.");
// this does not work
// error: Expression must evaluate to a node-set.
//var siblings = nav.Select("//m:parent/(m:first|m:second)", ns);
// but this does
var siblings = nav.Select("//m:parent/m:first|//m:parent/m:second", ns);
Console.Write($"Got {siblings.Count} children.");
Am I missing something? Is the first XPath expression wrong?
Is the first XPath expression wrong?
Yes, it's not valid XPath 1.0 syntax. You can't have a ( after a / in XPath 1.0.
You can achieve what you're trying to do, without repeating any node names, by using this path:
/m:root/m:parent/*[self::m:first or self::m:second]
Side note: avoid using // unless you have a specific reason to use it. It's bad for performance.

Trying to parse xml in Windows Phone 7.8 app but getting null value?

I am trying to parse a xml that I got as response to webservice response the xml is shown below
<GeneralSearchResponse>
<serverDetail></serverDetail>
<exceptions exceptionCount="1"></exceptions>
<clientTracking height="19" type="logo" width="106"></clientTracking>
<searchHistory></searchHistory>
<categories matchedCategoryCount="1" returnedCategoryCount="1">
<category id="0">
<name>bart</name>
<categoryURL>http://www.shopping.com/bart/products?oq=bart&linkin_id=7000610
</categoryURL>
<items matchedItemCount="1045" pageNumber="1" returnedItemCount="5">
<product id="130506037"></product>
<product id="104483377"></product>
<offer featured="false" id="tp-VCdOoO1RL6xICeRONqg==" smartBuy="false" used="false"></offer>
<offer featured="false" id="12evWWi57lddzFufngUWsg==" smartBuy="false" used="false"></offer>
<product id="96754577"></product>
</items>
<attributes matchedAttributeCount="5" returnedAttributeCount="5"></attributes>
<contentType>hybrid</contentType>
</category>
<intActualCategoryCount>4</intActualCategoryCount>
</categories>
<relatedTerms></relatedTerms>
</GeneralSearchResponse>
But when I am trying to parse using following code I am not able to get any descend or any node
XDocument xDoc = new XDocument();
xDoc = XDocument.Parse(data);
var xEle = xDoc.Root.Descendants("categories");
But xEle is not having any categories. Please let me know what is the issue??
Your XML has a default namespace - so the elements in it are in that namespace. The methods which find elements in LINQ to XML are namespace sensitive. You want:
XNamespace ns = "urn:types.partner.api.shopping.com";
XDocument xDoc = new XDocument();
xDoc = XDocument.Parse(data);
var xEle = xDoc.Root.Descendants(ns + "categories");

group node based on xpath predicate

I have strings of xpath like
/root/parent/child
/root/parent/child[1]
/root/parent/child[2]
In the c# code i am checking if the xpath exist in XmlDocument and cloning it like this
//Get the parent node of the node to be cloned
XmlNode NodeTobeCloned = xmlDoc.SelectSingleNode(oRow[0].ToString());
XmlNode DuplicateNode = NodeTobeCloned.CloneNode(true);
DuplicateNode.InnerText = sValue;
//Insert the node after the last child of a commoon parent
NodeTobeCloned.ParentNode.AppendChild(DuplicateNode);
I am getting a result like
<root>
<parent>
<child/>
<child/>
<child/>
</parent>
</root>
I want a result like
<root>
<parent>
<child/>
</parent>
<parent>
<child/> -- [1] -predicates elements
</parent>
<parent>
<child/> -- [2] -predicates elements
</parent>
</root>
how can i append to xmldocument using c#
thank you

XDocument AncestorAndSelf

I currently have an XML Structure that looks something like this
<Parent>
<Info>
<Info-Data></Info-Data>
<Info-Data2></Info-Data2>
</Info>
<Message>
<Foo></Foo>
<Bar></Bar>
</Message>
<Message>
<Foo/>
<Bar/>
</Message>
</Parent>
What I'm trying to accomplish is split each Message into it's own unique XDocument. I want it to be
<Parent>
<Info />
<Message />
</Parent>
I tried to do the following.
XDocument xDoc = XDocument.Parse(myXMLString);
IEnumerable<XElement> elements = xDoc.Descendants(xDoc.Root.Name.NameSpace + "Message");
foreach(XElement element in elements)
{
XDocument newDoc = XDocument.Parse(element.ToString());
}
Obviously this only gets me everything from Message and below. I tried using Ancestors and AncestorsAndSelf but they always include BOTH Messages. Is there a different call I should be making?
If your format is fixed like this, it's not so bad:
foreach(XElement element in elements)
{
XDocument newDoc = new XDocument
(new XElement(xDoc.Root.Name,
xDoc.Root.Element("Info"),
element));
// ...
}
It's not great, but it's not horrendous. An alternative is to clone the original document, remove all the Message elements, then repeatedly clone the "gutted" version and add one element at a time to the new clone:
XDocument gutted = new XDocument(xDoc);
gutted.Descendants(xDoc.Root.Name.Namespace + "Message").Remove();
foreach(XElement element in elements)
{
XDocument newDoc = new XDocument(gutted);
newDoc.Root.Add(element);
// ...
}

Filter XDocument more efficiently

I would like to filter with high performance XML elements from an XML document.
Take for instance this XML file with contacts:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="asistentes.xslt"?>
<contactlist evento="Cena Navidad 2010" empresa="company">
<contact type="1" id="1">
<name>Name1</name>
<email>xxxx#zzzz.es</email>
<confirmado>SI</confirmado>
</contact>
<contact type="1" id="2">
<name>Name2</name>
<email>xxxxxxxxx#zzzze.es</email>
<confirmado>Sin confirmar</confirmado>
</contact>
</contaclist>
My current code to filter from this XML document:
using System;
using System.Xml.Linq;
class Test
{
static void Main()
{
string xml = #" the xml above";
XDocument doc = XDocument.Parse(xml);
foreach (XElement element in doc.Descendants("contact")) {
Console.WriteLine(element);
var id = element.Attribute("id").Value;
var valor = element.Descendants("confirmado").ToList()[0].Value;
var email = element.Descendants("email").ToList()[0].Value;
var name = element.Descendants("name").ToList()[0].Value;
if (valor.ToString() == "SI") { }
}
}
}
What would be the best way to optimize this code to filter on <confirmado> element content?
var doc = XDocument.Parse(xml);
var query = from contact in doc.Root.Elements("contact")
let confirmado = (string)contact.Element("confirmado")
where confirmado == "SI"
select new
{
Id = (int)contact.Attribute("id"),
Name = (string)contact.Element("name"),
Email = (string)contact.Element("email"),
Valor = confirmado
};
foreach (var contact in query)
{
...
}
Points of interest:
doc.Root.Elements("contact") selects only the <contact> elements in the document root, instead of searching the whole document for <contact> elements.
The XElement.Element method returns the first child element with the given name. No need to convert the child elements to a list and take the first element.
The XElement and XAttribute classes provide a wide selection of convenient conversion operators.
You could use LINQ:
foreach (XElement element in doc.Descendants("contact").Where(c => c.Element("confirmado").Value == "SI"))

Categories