C# linq to xml, nested query with attributes - c#

Im really struggling to get my head round this.
Im using c#.
I want to get back an IEnumerable of products from an xml file.
Below is a sample of the xml structure.
I need to get a list of products that have the productEnriched custom attribute set as true.
Some products wont have any custom attribute section at all
my head has strated to hurt just thinking about it!
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://www.mynamespace.com" catalog-id="MvgCatalog">
<product>
<custom-attributes>
<custom-attribute attribute-id="productEnriched">true</custom-attribute>
</custom-attributes>
</product>
</category>
thanks for any help
To clear things up i have added a few more items to the example xml
I need to get a list of products
only products that have a custom-attribute element with the attribute productEnriched and value of true
some products in the xml wont have any custom-attribute or custom-attributes elements
some products will have it but with a value of false
i just need a list of products where it exists and has a value of true
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://www.mynamespace.com" catalog-id="MvgCatalog">
<product>
<upc>000000000000</upc>
<productTitle>My product name</productTitle>
<custom-attributes>
<custom-attribute attribute-id="productEnriched">true</custom-attribute>
<custom-attribute attribute-id="somethingElse">4</custom-attribute>
<custom-attribute attribute-id="anotherThing">otherdata</custom-attribute>
</custom-attributes>
</product>
</category>

I need to get a list of products only products that have a
custom-attribute element with the attribute productEnriched and value
of true some products in the xml wont have any custom-attribute or
custom-attributes elements some products will have it but with a value
of false i just need a list of products where it exists and has a
value of true
var xml = XElement.Load(#"your file.xml");
XNamespace ns = "http://www.mynamespace.com";
var products = xml.Elements(ns + "product");
var filtered = products.Where(
product =>
product.Element(ns + "custom-attributes") != null &&
product.Element(ns + "custom-attributes").Elements(ns + "custom-attribute")
.Any(
ca =>
ca.Value == "true" &&
ca.Attribute("attribute-id") != null &&
ca.Attribute("attribute-id").Value == "productEnriched"));
By the way, your XMLs are not valid - your opening tag (catalog) does not match your closing tag (category).
The format by itself is strange - is it your idea?
<custom-attributes>
<custom-attribute attribute-id="productEnriched">true</custom-attribute>
<custom-attribute attribute-id="somethingElse">4</custom-attribute>
<custom-attribute attribute-id="anotherThing">otherdata</custom-attribute>
</custom-attributes>
Why put an attribute name as an attribute value and attribute value as an element value? It looks bloated and kind of "reinvents" XML with no clear purpose.
Why not:
<custom-attributes>
<custom-attribute productEnriched="true"/>
<custom-attribute somethingElse="4"/>
<custom-attribute anotherThing="otherdata"/>
</custom-attributes>
Or:
<custom-attributes productEnriched="true" somethingElse="4" anotherThing="otherdata"/>
Or perhaps just use elements:
<product-parameters>
<productEnriched>true</productEnriched>
<somethingElse>4</somethingElse>
<anotherThing>otherdata</anotherThing>
</product-parameters>

Related

Avoid duplicate entry in xml file c#

I am trying to restrict duplicate entry to an XML file and below is the XML file.
<?xml version="1.0" standalone="yes"?>
<Info>
<Details>
<ID>Ryan</ID>
</Details>
<Details>
<ID>Danny</ID>
</Details>
</Info>
Now if I try to add Ryan or Danny again to the ID I should alert like user name already exists.
I'm using the below code and it doesn't work. strName is a string and has username value to be added. Can anyone provide suggestions?
XDocument xDoc = XDocument.Load(Server.MapPath("~/Info.xml"));
bool userExistsAlready = xDoc.Descendants("Details").Any(x => (string)x.Attribute("ID") == strName);
if (userExistsAlready)
{
//alert
}
Try this way:
bool userExistsAlready = xDoc.Descendants("Details")
.Elements("ID")
.Any(x => x.Value == "Ryan");
The problem with your code is that it tries to access attribute ID. But ID is in fact another XML element contained inside element <Details>.
You could set ID as an attribute of Details and then check if that entry exist using the XmlDocument method GetElementByID, or implement a for cycle that checks out the property InnerText of every element in the array resulting from the call to GetElementsByName method.

LINQ TO XML retrieving Child Element Value

I have the following XML
<ABC xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://ns.hr-xml.org/2007-04-15">
<ReceiptId>
<IdValue>123</IdValue>
</ReceiptId>
<ClientOrderId>
<IdValue>345</IdValue>
</ClientOrderId>
<AccessPoint>
<Description>My Description</Description>
</AccessPoint>
<ABCStatus>
<Status>Error</Status>
<Details>ERRORS:
Talent is already in an active process for this opening.
</Details>
<StatusDate>2015-08-05</StatusDate>
</ABCStatus>
</ABC>
I am trying to retrieve the element value 345 nested in IdValue and ClientOrderId
I have used the Linq to xml code in C# to retrieve the value with no luck
XDocument XMLResults = XDocument.Parse(sResult);
var sClientOrderID =
from nodeAElem in XMLResults.Root.Elements("ABC")
from nodeA1Elem in nodeAElem.Elements("ClientOrderId")
from nodeA11Elem in nodeA1Elem.Elements("IdValue")
select nodeA11Elem.Value;
also need to retrieve the Status Elements value which is Error for the above xml.
Any help is greatly appreciated
Your XML document is using a namespace, you have to use it in your query to make it work.
Root already brings you to ABC element, so you don't have to call Elements("ABC")
You're looking for single value, so you probably want to use Element instead of Elements.
var ns = (XNamespace)"http://ns.hr-xml.org/2007-04-15";
var sClientOrderID = (int)XMLResults.Root
.Element(ns + "ClientOrderId")
.Element(ns + "IdValue");

Locating a value in XML

I have an xml file loaded into an XDocument that I need to extract a value from, and I'm not sure of the best way to do it. Most of the things I'm coming up with seem to be overkill or don't make good use of xml rules. I have the following snippet of xml:
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
There can be any number of <entry> nodes, and they will all follow a similar pattern. The value under the root attribute on the templateId element contains a known UID that identifies this entry as the one I want. I need to get the reference value.
My thought is to find the correct templateID node, back out to the observation node, find <valuexsi:type="ED"> and then get the reference value. This seems overly complex, and I am wondering if there is another way to do this?
EDIT
The xml I receive can sometimes have xml nested under the same node name. In other words, <observation> may be located under another node named <observation>.
You have problems, because your document uses Namespaces, and your query is missing them.
First of all, you have to find xsi namespace declaration somewhere in your XML (probably in the most top element).
It will look like that:
xmlns:xsi="http://test.namespace"
The, take the namespace Uri and create XNamespace instance according to it's value:
var xsi = XNamespace.Get("http://test.namespace");
And use that xsi variable within your query:
var query = from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
let tId = o.Element("templateId")
where tId != null && (string)tId.Attribute("root") == "2.16.840.1.113883.10.20.6.2.12"
let v = o.Element("value")
where v != null && (string)v.Attribute(xsi + "type") != null
let r = v.Element("reference")
where r != null
select (string)r.Attribute("value");
var result = query.FirstOrDefault();
I have tested it for following XML structure:
<root xmlns:xsi="http://test.namespace">
<entries>
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
</entries>
</root>
The query returns #121071 for it.
For your input XML you will probably have to change first line of query:
from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
to match <observation> elements from your XML structure.
Would something along the lines of the following help?
XDocument xdoc = GetYourDocumentHere();
var obsvlookfor =
xdoc.Root.Descendants("observation")
.SingleOrDefault(el =>
el.Element("templateId")
.Attribute("root").Value == "root value to look for");
if (obsvlookfor != null)
{
var reference = obsvlookfor
.Element("value")
.Element("reference").Attribute("value").Value;
}
My thought is as follows:
Pull out all the observation elements in the document
Find the only one (or null) where the observation's templateId element has a root attribute you're looking for
If you find that observation element, pull out the value attribute against the reference element which is under the value element.
You might have to include the Namespace in your LINQ. To retrieve that you would do something like this:
XNamespace ns = xdoc.Root.GetDefaultNamespace();
Then in your linq:
var obsvlookfor = xdoc.Root.Descendants(ns + "observation")
I know I had some issues retrieving data once without this. Not saying its the issue just something to keep in mind particularly if your XML file is very in depth.

LINQ query to get XML elements when an attribute is not present

I have this XML:
<Config>
<EmpFieldsMap>
<Employee>
<Field>
<Name update = "false">EmpNumber</Name>
</Field>
<Field>
<Name insert = "true">EmpName</Name>
</Field>
<Field>
<Name insert = "true">EmpDesignation</Name>
</Field>
</Employee>
</EmpFieldsMap>
</Config>
My application will do an an INSERT or UPDATE for which the fields will come from this xml.
Each tag will have either the insert or the update attribute as shown in the snippet above.
For Insert all the tags that have the attribute
insert = "true"
and the tags that don't have this attribute, in this case the 'EmpNumber', have to be considered.
The same applies for update.
This code gives me all the tags with the insert attribute set to true:
insertTags = from p in xml.Element("Config").Element("EmpFieldsMap").Elements("Field")
where p.Element("Name").Attribute("insert") != null
&& p.Element("Name").Attribute("insert").Value == "true"
select p.Element("Name").Value;
Removing the check for null
insertTags = from p in xml.Element("Config").Element("EmpFieldsMap").Elements("Field")
where p.Element("Name").Attribute("insert").Value == "true"
select p.Element("Name").Value;
gives
Object Reference not set to an instance
error.
I am having trouble composing a query that will also include the tags where the attribute is not present.
Can someone please help me with this?
Regards.
insertTags = from p in xml.Element("Config").Element("EmpFieldsMap").Elements("Field")
where (p.Element("Name").Attribute("insert") ?? "true") == "true"
select p.Element("Name").Value;
With XPath and Linq it even simpler:
XPathSelectElements(#"Config/EmpFieldsMap/Employee/Field/Name[#insert='true']")
Also for this particular xml you can use global search for name elements:
var insertTags = xdoc.XPathSelectElements(#"//Name[#insert='true']")
.Select(n => (string)n);
Or with Linq query syntax:
var insertTags = from n in xdoc.Descendants("Name")
where (string)n.Attribute("insert") == "true"
select (string)n;
When you cast node value to string, it will not throw exception if node is missing. Simply null will be returned. So, you don't need all that stuff (which is btw even not compilable):
(p.Element("Name").Attribute("insert") ?? "true") == "true"
One more edit. If you are dealing with booleans, then use booleans instead of strings:
var insertTags = from n in xdoc.Descendants("Name")
where (bool?)n.Attribute("insert") == true
select (string)n;
How it works? Nullable boolean will have null value for missing attributes. Comparing bool? which do not have value with any boolean value produces false. So, you will get only those elements, which have required attribute, and which have true for that attribute.

How to search using getelementid in xml

I want to search through XML Using GetElementId .
I have an XML file with attributes associated with each element. Elements name may defer but each element will have unique Id.
For example:
<root>
<secondRoot>
<Person UniqueID='A112' Name='Fred'><FeMale>I am Female</FeMale></Person>
<Person UniqueID='A111'><Male>I am male</Male></Person>
<Person SSN='A222' Name='Tom'/>
<Customer id='A111'/>
<Customer id='A222334444'/>
<Team members='A222334444 A333445555'/>
<Random/>
</secondRoot>
</root>
In the above XML i have UniqueID attribute associated with several elements. I want to search elements with UniqueID attribute . Eventhough i tried using following DTD its not sufficient.
<!DOCTYPE root [
<!ELEMENT root ANY>
<!ATTLIST Person UniqueID ID #REQUIRED>
]>
The Problem is UniqueID may occur in several elements attribute list.
I need to avoid a situation which i have to declare every occurances of elements that have UniqueID attribute in DTD.
Can Anyone suggest any idea for that?
Thanks
Try using LINQ to XML, no need for DTD.
// XML data
var xml = "<root><secondRoot><Person UniqueID='A112' Name='Fred'><FeMale>I am Female</FeMale></Person><Person UniqueID='A111'><Male>I am male</Male></Person> <Person SSN='A222' Name='Tom'/> <Customer id='A111'/> <Customer id='A222334444'/> <Team members='A222334444 A333445555'/> <Random/></secondRoot></root>";
var doc = XDocument.Parse(xml);
// Get all nodes that have UniqueID
var nodes =
from element in doc.Descendants()
where element.Attribute("UniqueID") != null
select element;
The nodes list will contain all nodes that have the UniqueID attribute.

Categories

Resources