C# Find XElement Descendant based on multiple attributes - c#

I have to add information to an existing XML file. The data is going to be underneath an existing node. This has to do with Patient data, and I have to find the existing patient within the XML, so I can add the subsequent data to it. This data is encapsulated within a "PATIENTDETAILS" element.
While I found many articles on how to find a descendant via a single attribute, I need to use multiple attributes can try as I might, I can't seem to find how to use multiple attributes.
This is my current query (C#):
XElement patient = xmlDoc.Descendants(ns + "_PATIENTDETAILS").ToList().WHERE
(x => (string)x.Element(ns + "_PatientName") == currentPatientName).FirstOrDefault();
I need to add "_PatientAccNo", "_HicNo" and "_MedRecNo" to the where clause to ensure I find the right _PATIENTDETAILS before adding a new element beneath that patient with the new data.
I'm adding the new element after this query by doing:
XElement serviceLines = patient.Element("_PATIENTDETAILS");
xmlDoc.Element("_OCROUTPUT).Element("_PATIENTDETAILS").Add(new XELEMENT("_SERVICELINES",
new XElement(name, data),
Blah blah blah
If someone can give me an example of using multiple where clauses in finding a Descendant, I'd appreciate it.

You can combine conditions with a logical and which is && in C#.
XElement patient = xmlDoc.Descendants(ns + "_PATIENTDETAILS")
.Where(x => (string)x.Element(ns + "_PatientName") == currentPatientName &&
(string)x.Element(ns + "another element") == anotherValue &&
(string)x.Element(ns + "yet another element") == yetAnotherValue)
.FirstOrDefault();
See also: Conditional logical AND operator &&.
And also the .ToList() in there can be dropped. It adds no value.

Related

How to convert XElement to lowercase?

This is my code. I essentially want it to return the descendants regardless of the string case.
XElement doc = XElement.Load(myReader.ReadSubtree());
IEnumerable<XElement> query1 = doc.Descendants(es + family).Descendants(es + parameterName.Trim());
If family is "Jones", but in the XML it's "jones", then nothing is found. The same thing happens with parameterName.
Is there any way to change XElement doc to lowercase? That way I can use String.ToLower on both family and parameterName.
I found this reply, but I don't know how to implement that solution here.
Since I rarely use linq, and I know that the previous code works, I tried going step-by-step and used the where extension, but it didn't return anything:
IEnumerable<XElement> query1 = doc.Descendants(es + family).Where(node => (string)node.Attribute(family) == family)
.Descendants(es + parameterName.Trim()).Where(node => (string)node.Attribute(parameterName) == parameterName);
Since this change didn't return anything, I didn't go beyond that.
I also tried the following code, and I get a run error in the first where extension:
IEnumerable<XElement> query1 =
doc.Descendants(es + family).Where(node => node.Attribute(family).ToString().ToLower() == family.ToLower())
.Descendants(es + parameterName.Trim()).Where(node2 => node2.Attribute(parameterName).ToString().ToLower() == parameterName.ToLower());
You could use the Equals method it allows ignoring case:
.Where(node => ((string)node.Attribute(family)).Equals(family,StringComparison.OrdinalIgnoreCase))
I used Ordinal comparison but there are others available in the StringComparison enum.

Using C # LINQ to Select within 2 tags that has a descendant node equal to a certain value?

I'm trying to select all objects within 2 tags specifically <AR>'s that contains an element that's a descendant of <AR>: <RL> with a certain value say 2. <RL> can be buried an arbitrary number of levels within <AR>'s, but will always be within <AR>. How can I do this in LINQ?
EX1:
<ARS>
<AR>
<EI> </EI>
<RL>5</RL>
</AR>
<AR>
<EI> </EI>
<RL>2</RL>
</AR>
</ARS>
Result :
<AR>
<EI> </EI>
<RL>2</RL>
</AR>
I tried using
IEnumerable<XNode> test_var = from result in doc.Descendants("AR")
where result.DescendantNodes()
But go from here, but this threw an error msg
var test_var = from result in doc.Descendants("AR")
where result.Descendants("RL").Any(x => (int)x == 2)
select result;
First problem is you need a select statement, such as adding select result.
Additionally your where needs to be a boolean. It sounds like you are looking for existence, which is frequently handled via the Any() extension method.
var searchString = "2";
IEnumerable<XElement> test_var = from result in doc.Descendants("AR")
where result.Descendants("RL").Any(xelm => xelm.Value == searchString)
select result;

Linq to XML: Queries for If blocks C#

I've got XML set up like this
<customers>
<customer id="1">
<name title="Mr" first="John" last="Smith" />
<contact number="07123123123" email="john.smith#johnsmith.com" />
<address postcode="E1 1EW">1 Paper Street, London, England, GB</address>
</customer>
(...)
</customers>
I'm trying to query it with Linq to XML for learning purposes. So far I can XDocument.Load the file fine and add/remove etc. But I can't seem to figure out a way to query my XML documents for use in an if block. for example something like (Pseudo code):
XDocument document = XDocument.Load("People.xml");
if(
exists(
document.customers.customer.name.first.value("john")
&& document.customers.customer.name.last.value("smith")
)
)
{
bool exists = true;
}
Whatever I try something the compiler will either laugh at me or spit out something about how it cannot implicitly convert an Ienumerable bool to a bool.
I've been trying many combinations of things from many different google searches for a while now and I think guessing is starting to do more harm than good, can anybody provide a C# snippet that would work in an if block for my xml setup? Just to see if first and last name exist together in the same node. Usually once I see something actually work I can take it from there. Most of the questions I find on the net are only searching to see if the entire node exists or are only searching for one attribute and I just can't seem to tailor it.
(Before anybody bursts into flames over my XML structure, this is not for production purposes, I just want to get a grasp of using this in case I need to in an upcoming project.)
Bonus love for anybody that can link any documentation that's not MSDN (I already have about 10 tabs open on it) and involves some good low level/beginner tutorials on Linq to XML.
To check for John Smith exists as a customer in your XML you ll use
XDocument doc = XDocument.Load(Path);
var customers = doc.Descendants("customer");
//To Check for John Smith
if (customers.Elements("name")
.Any(E => E.Attribute("first").Value == "John"
&& E.Attribute("last").Value == "Smith"))
{
//Do your thing
}
Created a boolean called check to hold the result.
do a count on how many elements called name have attributes First=john and last=smith
Then set the result to check variable.
BTW your XML was missing a " in front of John. That will cause you problems :)
bool check;
var res = XDocument.Load(#"c:\temp\test.xml");
var results = res.Descendants("name")
.Where(x => x.Attribute("first").Value == "john" && x.Attribute("last").Value == "smith")
.Select(x => x.Elements()).Count();
check = results != 0;
XPath is your friend
using System.Xml.XPath;
...
void foo(){
XDocument document = XDocument.Load("People.xml");
var firstCustomerNode = document.XPathSelectElement(
"/customers/customer[0]/name"
);
var hasfirstNameAndLastName = firstCustomerNode.Attribute("firstname") != null && firstCustomerNode.Attribute("lastname") != null;
if(hasfirstNameAndLastName)
{
}
}
Please note that you may also use a schema to validate your Xml, but it's more complex to write.
If you don't want to use XPath, you may also write:
var firstCustomerNode = document.Root.Element("Customers").Elements("customer").First().Element("name");
But honestly, the XPath query is far more readable, and will simplify the error check. This code suppose all the elements up to the target node exists. If not, your code will fail with a poor NullReferenceException.
You need to start by querying the Document.Root.Descendants. Then a method such as IfExists
var nodes = document.Root.Descendants("customer");
bool IfExists(string FirstName, string LastName) {
return nodes.Elements("name").Any(node =>
node.Attribute("first").Value.Equals(FirstName) &&
node.Attribute("last").Value.Equals(LastName));
}
You may want to add exception handling in case an attribute is missing or contains an empty value.

Parsing XML stream using LINQ

I found a lot of examples and questions on this topic but what ever I try the result is blank.
From the xml I need to get out the first 'listing' element from there I need just the DisplayName and Address info.
var listings = from c in xdoc.Elements("listing") select c;
You are missing the namespace in your query. The name of the node is not listing it is namespace + listing
So you need to get the namespace of the wp element or use the local name property:
var listings = from c in xdoc.Descendants()
where c.Name.LocalName == "listing"
select c;
or you need to get the namespace and add it to the query
XNamespace ns = // namespace name here
var listings = from c in xdoc.Descendants(ns + "listing") select c;
listing will be defined as some variation of IEnumerable<XElement>. The exact type will vary depending on which query you used, but the important part is it will derive from IEnumerable<>.
The other problem is your use of the Elements() method. Elements() will only search the children of node defined by xdoc. Descendants() will also look in the child of the children and all other child nodes.
UPDATE 1 - Added more details on getting specific nodes from XML
Getting the address and displayname is basically the same process, in fact you can just add it to the original query or use a different query (it all depends on how you want to use it later - will you ever need the other elements of wp:listing or just the displayname and address?
If you might need others, then it seems logical to do it as a different query so you can query your result later. Readability also comes into play as nesting multiple queries inside of each other can make it difficult to read in my opinion.
Getting Address is the hardest part. You need to decide how you want it... do you want a XElement with all of the address parts as child nodes? Or do you want to construct a new object? Or do you just need a concatenated string?
But the general query would be something like this:
var result = from listing in listings
select new
{
Name = listing.Element(ns + "displayname").Value,
Address = listing.Element(ns + "address")
};
This would give you an IEnumerable<'a'> with a defined as an anonymous type consisting of a Name property (as a String) and an Address property (as an XElement). If you want something else, you have to replace Address = listing.Element(ns + "address") with appropriate line(s) of code.
If you just need another anonymous type, then you need to put a nested query in that line:
Address = from part in listing.Elements(ns + "address")
select new
{
FullStreet = part.Element(ns + "fullstreet").Value,
HouseNumber = part.Element(ns + "house").Value,
Street = part.Element(ns + "street").Value,
StreetType = part.Element(ns + "streettype").Value,
// continue for all Elements you need/want
};
Or you could just create an Address class and call the constructor or a factory method in the query and pass the XElement or the address parts.
Try to use Descendants instead of Elements
var listings = from c in xdoc.Descendants("listing") select c;

LINQ to XML - Where clause

I have an XML file with 2 types of information - Locations and job types which is determined by the SLvl value. I wish to bind the SearchTxt value for these to 2 drop downs (one for locations, one for job types) to be used as filters on my page.
The problem is I cant quite get my where clause to filter on the SLvl value. With the where clause in no results are returned. If I remove it the query does return all the text values.
C#
using System.Xml.Linq;
using System.Linq;
.....
// Loading from file
XDocument loaded = XDocument.Load(#"http://[LINKREMOVED]/vacancies.aspx");
// Query the data
var q = (from c in loaded.Descendants("items")
where c.Element("SLvl").ToString() == "0"
select c.Element("SearchTxt").ToString()).Distinct();
// Populate drop down
foreach(string name in q)
{
ddlLocation.Items.Add(new ListItem(name, name));
}
XML:
<VacancyMatch>
<items>
<SearchID>60</SearchID>
<SearchTxt>Scotland</SearchTxt>
<ParentID>0</ParentID>
<SearchCatID>1</SearchCatID>
<SLvl>1</SLvl>
<SubCat>1</SubCat>
</items>
<items>
<SearchID>92</SearchID>
<SearchTxt>Accounting</SearchTxt>
<ParentID>60</ParentID>
<SearchCatID>2</SearchCatID>
<SLvl>2</SLvl>
<SubCat>2</SubCat>
</items>
... More items here
</VacancyMatch>
I guess the problem is that the data is at the same level? Its my first time using LINQ to XML so any help is greatly appriciated.
Note:
XML is provided by a third party so formatting is up to them.
Drop the .ToString() and use .Value property instead:
var values = loaded.Descendants("items")
.Where(i => i.Element("SLvl").Value == "0")
.Select(i => i.Element("SearchTxt").Value)
.Distinct();
Calling ToString() on XElement will return entire node as text. For example, if we change i.Element("SearchTxt").Value in the query above to i.Element("SearchTxt").ToString() it will produce strings such as:
<SearchTxt>Accounting</SearchTxt>
Accessing Value property will extract node's inner text - "Accounting" in this case.
This:
where c.Element("SLvl").ToString() == "0"
should be:
where c.Element("SLvl").Value == "0"
You can't get a value of an element with "ToString()" method, you need to read it's "Value" property instead.
Same goes for any other lines where you are trying to get a value of an element.
Hope it helps.
I notice one problem, you use ToString() on the XElements which is not exactly what you want, I think :), to get the text content of a XElement use the Value property.
http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.value.aspx

Categories

Resources