Accessing child node value with parent reference with LINQ to XML - c#

I am stuck with accessing child node value. Below is the XML structure and the code to create new contact.
<Order xmlns="http://example.com">
<MiscContact>
<MiscContact>
<ContactType>MailingContact</ContactType>
<Contact>
<Name>
<First>JIM</First>
<Last>RON</Last>
<FullName>RON JIM</FullName>
</Name>
<IsValid>false</IsValid>
</Contact>
</MiscContact>
</MiscContact>
<ExportForm>
<Contact>
<Name>
<First>JIM</First>
<Last>RON</Last>
<FullName>RON JIM</FullName>
</Name>
<IsValid>false</IsValid>
</Contact>
</ExportForm>
</Order>
Code to create new contact only for <MiscContact>:
XNamespace Namespace = "http://online.us.com";
var MiscContact = from mc in xmlDoc.Descendants(Namespace + "Contact")
where mc.Parent.Name.Equals("MiscContact")
select new Contact
{ Name = ob.Element(Namespace + "Name").Value }
Issue i encountered is that even though i have where clause to select only contact whose parent is MiscContact, but contact section both from <MiscContact> and <ExportForm> are getting loaded.
Any idea how to resolve this issue?

Your code appears to be working correctly with the where clause and pulls the Name element from MiscContact -> Contact -> Name. I think your problem is that you're using .Value in the end, which concatenates all these values together:
<Name>
<First>JIM</First>
<Last>RON</Last>
<FullName>RON JIM</FullName>
</Name>
The result is "JIMRONRON JIM". If you need the "FullName" then you should use:
mc.Element(Namespace + "Name")
.Element(Namespace + "FullName").Value
Replace "FullName" with "First" or "Last" as needed.

Related

How to get xml node value based on attribute value c# , asp.net?

how to get xmlnode value based on attribute value in C#.
<Products>
<Error>0</Error>
<Product link="OWN">
<ProductCode>CA</ProductCode>
<ProductType>Account</ProductType>
<ProductSubtype>Current Account</ProductSubtype>
<ProductDescriptionEng>Current Account</ProductDescriptionEng>
<ProductNumber>1234567</ProductNumber>
<ProductCurrency></ProductCurrency>
<ProductCurrencyCode>01</ProductCurrencyCode>
<ProductBalance>899293.003</ProductBalance>
<LoanRef></LoanRef>
<OldLoanRef></OldLoanRef>
<ProductStandardID>00010001</ProductStandardID>
<OldLoanRef></OldLoanRef>
<ProductStatusCode>00</ProductStatusCode>
<ProductStatus>OPEN</ProductStatus>
<ProductCategory>Deposit Account</ProductCategory>
</Product>
</Product>
in the above example i would like to get value of Product Link "OWN" based on passing attribute value (1234567) 1234567.
I have tried following code but it doesn't return value "OWN" from 'Product link" node.
string _accountsXml = GetProducts();
_xmlDoc.LoadXml(_accountsXml);
_productLinkType = _xmlDoc.SelectSingleNode(string.Format("//Products[../ProductNumber = '{0}']", prodNumber));
You do have to add code to get element's attribute value there, possibly minding the case when the right element was not found. Plus be careful with the element names - both in the xpath expression and in the XML.
The following test code would have "OWN" in the link variable:
var doc = XDocument.Parse(#"<?xml version=""1.0""?>
<Products>
<Error>0</Error>
<Product link=""Global""/>
<Product link=""OWN"">
<Some/>
<ProductNumber>1234567</ProductNumber>
<Nodes/>
</Product>
<Product link=""External"">
<ProductNumber>777</ProductNumber>
</Product>
</Products>");
var id = 1234567;
var link = doc
.XPathSelectElement($"//Product[ProductNumber = '{id}']")
?.Attribute("link")
?.Value;
Console.WriteLine($"Product link: {link ?? "not found"}");
Try this
_productLinkType = _xmlDoc.SelectSingleNode(string.Format("//Products[..//ProductNumber = '{0}']", prodNumber));
or
var xDoc = XDocument.Load(_accountsXml);
var productLinkType = xDoc.XPathSelectElement(string.Format("//Products[..//ProductNumber = '{0}']", prodNumber)).Element("Product").Attribute("link").Value;
The closing tag in your example is Product it should be Products

Linq to XML read method throws NullReferenceException using C# in VS2017

This is my XML structure:
<root>
<report>
<results>
<result>
<name> name 1 </name>
</result>
<result>
<name> name 2 </name>
</result>
</results>
</report>
</root>
I want to read the information within result and create an object to set it's attribute to the value. This is my code to do this:
Read from XML:
XDocument rootDocument = XDocument.Load(filePath)
var vul = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name").Value
};
Create new object:
foreach(var r in vul)
{
Object a = new Object()
{
Name = r.Name
};
}
The Problem is that this produces a NullReferenceException when calling
rootDocument.Descendants("result")
Thank you in advance!
I am guessing that your select is trying to access an element that does not exist.
For example there is a missing name element from your original xml:
e.g. This xml will fail
<root>
<report>
<results>
<result>
<name> name 1 </name>
</result>
<result>
<name> name 2 </name>
</result>
<result>
<surname> name 2 </surname> //will fail when loop gets here
</result>
</results>
</report>
</root>
If it is possible that name element will not always exist you can update the code to handle null
var vul = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name")?.Value, //this will return null
};
..as r.Name can be null you need to account for this in the for loop.
foreach (var r in vul)
{
string Name = r.Name ?? string.empty;
}
I found the cause of this Error. I don't know why but calling the value of an element causes it. I changed
Name = r.Element("name").Value
to
Name= r.Element("name")

Linq to XML to retrieve value based on Attribute

I have a Linq to Xml query that needs to retrieve a value based on the attribute value of a particular node. I'm trying to retrieve a list of items and one of the nodes has an attribute that I can't seem to find a way to get the value.
Here's the XML:
<codelist-items>
<codelist-item>
<code>1</code>
<name>
<narrative>Planned start</narrative>
<narrative xml:lang="fr">Début prévu</narrative>
</name>
<description>
<narrative>
The date on which the activity is planned to start, for example the date of the first planned disbursement or when physical activity starts.
</narrative>
</description>
</codelist-item>
<codelist-item>
<code>2</code>
<name>
<narrative>Actual start</narrative>
<narrative xml:lang="fr">Début réel</narrative>
</name>
<description>
<narrative>
The actual date the activity starts, for example the date of the first disbursement or when physical activity starts.
</narrative>
</description>
</codelist-item>
</codelist-items>
I'm only displaying 2 items to keep it short. And here is my Linq query to try and retrieve the value from "name/narrative" where there is a "xml:lang='fr'" attribute:
XElement xelement = XElement.Load(xmlFile);
var elements = from adt in xelement.Elements("codelist-items").Elements("codelist-item")
select new ActivityDateType
{
Code = (string)adt.Element("code"),
NameEng = (string)adt.Element("name").Element("narrative"),
NameFra = (string)adt.Element("name").Element("narrative[#xml:lang='fr']"),
Description = (string)adt.Element("description")
};
return elements;
Anyone know how to get the value for NameFra?
Thanks
You can either use LINQ FirstOrDefault() with predicate that filters the element by its attribute value :
NameFra = (string)adt.Element("name")
.Elements("narrative")
.FirstOrDefault(o => (string)o.Attribute(XNamespace.Xml+"lang") == "fr"),
Or use XPathSelectElement() extension to execute your XPath expression which already contains attribute filtering logic :
NameFra = (string)adt.Element("name")
.XPathSelectElement("narrative[#xml:lang='fr']", nsmgr),
The latter can be simplified further to the following :
NameFra = (string)adt.XPathSelectElement("name/narrative[#xml:lang='fr']", nsmgr),
nsmgr assumed has previously been declared as follow :
var nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr was needed because the XPath contains prefix xml (XPathSelectElement() complained when I use the overload which accepts just XPath string argument without namespace manager).

Searching through XML to find a list of items

I have an xml doc as such:
<?xml version="1.0" encoding="utf-8" ?>
<Categories>
<Category>
<Name>Fruit</Name>
<Items>
<Item>Apple</Item>
<Item>Banana</Item>
<Item>Peach</Item>
<Item>Strawberry</Item>
</Items>
</Category>
<Category>
<Name>Vegetable</Name>
<Items>
<Item>Carrots</Item>
<Item>Beets</Item>
<Item>Green Beans</Item>
<Item>Bell Pepper</Item>
</Items>
</Category>
<Category>
<Name>Seafood</Name>
<Items>
<Item>Crab</Item>
<Item>Lobster</Item>
<Item>Shrimp</Item>
<Item>Oysters</Item>
<Item>Salmon</Item>
</Items>
</Category>
</Categories>
I would like to be able to search on a term such as Category.Name = Fruit and get back the list of the Fruit Items.
Here is the incomplete code I've started so far:
string localPath = Server.MapPath("~/App_Data/Foods.xml");
XmlDocument doc = new XmlDocument();
doc.Load(localPath);
XmlNodeList list = doc.SelectNodes("Categories");
//Do something here to search the category names and get back the list of items.
This is my first attempt at parsing through XML so I'm a bit lost. Note: the application I am working on uses .Net 2.0
I'd suggest to read about XPath as you're limited to .NET 2.0, moreover XPath is very useful to work with XML even in more general context (not limited to .NET platform only).
In this particular case XPath become useful because SelectNodes() and SelectSingleNode() method accept XPath string as parameter. For example, to get all <Item> that corresponds to category name "Fruit" :
string localPath = Server.MapPath("~/App_Data/Foods.xml");
XmlDocument doc = new XmlDocument();
doc.Load(localPath);
XmlNodeList items = doc.SelectNodes("Categories/Category[Name='Fruit']/Items/Item");
foreach(XmlNode item in items)
{
Console.WriteLine(item.InnerText);
}
You can see XPath as a path, similar to file path in windows explorer. I'd try to explain only the bit that is different from common path expression in the above sample, particularly this bit :
...\Category[Name='Fruit']\...
the expression within square-brackets is a filter which say search for <Category> node having child node <Name> equals "Fruit".
You are on the right path. However, you would need to load the 'Categories' node first, then you can get it's child nodes.
I have added a filter to return only nodes where the name is "Fruit".
XmlNode cat = doc.SelectSingleNode("Categories");
var list = cat.SelectNodes("Category").Cast<XmlNode>()
.Where(c => c.SelectSingleNode("Name").InnerText == "Fruit");
foreach ( XmlNode item in list )
{
// process each node here
}

C# XPath Not Finding Anything

I'm trying to use XPath to select the items which have a facet with Location values, but currently my attempts even to just select all items fail: The system happily reports that it found 0 items, then returns (instead the nodes should be processed by a foreach loop). I'd appreciate help either making my original query or just getting XPath to work at all.
XML
<?xml version="1.0" encoding="UTF-8" ?>
<Collection Name="My Collection" SchemaVersion="1.0" xmlns="http://schemas.microsoft.com/collection/metadata/2009" xmlns:p="http://schemas.microsoft.com/livelabs/pivot/collection/2009" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FacetCategories>
<FacetCategory Name="Current Address" Type="Location"/>
<FacetCategory Name="Previous Addresses" Type="Location" />
</FacetCategories>
<Items>
<Item Id="1" Name="John Doe">
<Facets>
<Facet Name="Current Address">
<Location Value="101 America Rd, A Dorm Rm 000, Chapel Hill, NC 27514" />
</Facet>
<Facet Name="Previous Addresses">
<Location Value="123 Anywhere Ln, Darien, CT 06820" />
<Location Value="000 Foobar Rd, Cary, NC 27519" />
</Facet>
</Facets>
</Item>
</Items>
</Collection>
C#
public void countItems(string fileName)
{
XmlDocument document = new XmlDocument();
document.Load(fileName);
XmlNode root = document.DocumentElement;
XmlNodeList xnl = root.SelectNodes("//Item");
Console.WriteLine(String.Format("Found {0} items" , xnl.Count));
}
There's more to the method than this, but since this is all that gets run I'm assuming the problem lies here. Calling root.ChildNodes accurately returns FacetCategories and Items, so I am completely at a loss.
Thanks for your help!
Your root element has a namespace. You'll need to add a namespace resolver and prefix the elements in your query.
This article explains the solution. I've modified your code so that it gets 1 result.
public void countItems(string fileName)
{
XmlDocument document = new XmlDocument();
document.Load(fileName);
XmlNode root = document.DocumentElement;
// create ns manager
XmlNamespaceManager xmlnsManager = new XmlNamespaceManager(document.NameTable);
xmlnsManager.AddNamespace("def", "http://schemas.microsoft.com/collection/metadata/2009");
// use ns manager
XmlNodeList xnl = root.SelectNodes("//def:Item", xmlnsManager);
Response.Write(String.Format("Found {0} items" , xnl.Count));
}
Because you have an XML namespace on your root node, there is no such thing as "Item" in your XML document, only "[namespace]:Item", so when searching for a node with XPath, you need to specify the namespace.
If you don't like that, you can use the local-name() function to match all elements whose local name (the name part other than the prefix) is the value you're looking for. It's a bit ugly syntax, but it works.
XmlNodeList xnl = root.SelectNodes("//*[local-name()='Item']");

Categories