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.
Related
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.
I have a very large XML document that has some missing nodes.
Good XML:
<wd:Job_Family wd:Descriptor="Research/Extension">
<wd:Home_Phone wd:Descriptor="+1 (555) 555-0731">
<wd:ID wd:type="WID">89bfac800b6b41da94e1d1a22b14e66a</wd:ID>
</wd:Home_Phone>
<wd:Home_Address wd:Descriptor="1 Beverly Dr">
Bad XML:
<wd:Job_Family wd:Descriptor="Research/Extension">
***MISSING ***
<wd:Home_Address wd:Descriptor="1 Beverly Dr">
The way I had been selecting the data was like this:
List<Employee> employee = new List<Employee>();
try
{
// Get the xml file as stream
StreamReader reader = new StreamReader(outputFileNameAndPath);
// Read the whole contents and return as a string
string xmlString = reader.ReadToEnd();
XDocument doc = XDocument.Parse(xmlString);
XNamespace wd = "urn:com.something.report/Worker_ID_Data_-_Store";
IEnumerable<XElement> worker = doc.Descendants(wd + "Report_Entry");
var query = (from x in doc.Descendants(wd + "Report_Entry")
let jobFamilyAttribute = x.Element(wd + "Job_Family_Group").Attributes(wd + "Descriptor").FirstOrDefault()
select new Employee
{
JobFamily = jobFamilyAttribute.Value
});
employee.AddRange(query);
}
catch (Exception ex)
{
log.Error("An error occurred while trying to parse the XML: " + ex);
}
return employee;
That works great. There are some additional nodes I select, but for simplicity sake, this is enough.
Now, when I try to select a node that is missing (which might be 1 out of 1000 records), I get errors for Object reference not set to an instance of an object.
That makes sense, since the node being selected isn't there.
After reading many postings, it looks like I should do a ternary operator to account for the null parent. Something like this:
let homePhoneAttribute = x.Element(wd + "Home_Phone") == null ? "" : x.Element(wd + "Home_Phone").Attributes(wd + "Descriptor").FirstOrDefault()
That isn't quite right:
Type of conditional expression cannot be determined because there is no implicit conversion between 'string' and 'System.Xml.Linq.XAttribute'
Even if I cast it to a string or object, it compiles, but then I only get the very first value for all records when I addrange:
let homePhoneAttribute = x.Element(wd + "Home_Phone") == null ? (string)"" : x.Element(wd + "Home_Phone").Attributes(wd + "Descriptor").FirstOrDefault().Value
So, my long winded question is, how do I properly cast and account for null nodes while still being able to select with a let?
I think it has to be some kind of DefaultIfEmpty() casting?
You can use the following expression :
let homePhoneAttribute = (string)x.Elements(wd + "Home_Phone")
.Attributes(wd + "Descriptor")
.FirstOrDefault()
By using x.Elements() instead of the singular form Element() you can avoid exception in case the element is not found. And then, by casting FirstOrDefault() return value to string you can avoid exception in case the return value is null.
dotnetfiddle demo
The demo shows that homePhoneAttribute variable simply contains empty string when wd:Home_Phone element is missing. No exception thrown.
UPDATE :
If you want multiple attributes value (List<string>) instead of just just the first, you can modify the above LINQ a bit as follow :
let homePhoneAttribute = x.Elements(wd + "Home_Phone")
.Attributes(wd + "Descriptor")
.Select(o => o.Value)
.ToList()
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;
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.
I find it puzzling to determine the best way to parse some XML. It seems they are so many possible ways and none have really clicked with me.
My current attempt looks something like this:
XElement xelement = XElement.Parse(xmlText);
var name = xelement.Element("Employee").Attribute("name").Value;
So, this works. But it throws an exception if either the "Employee" element or the "name" attribute is missing. I don't want to throw an exception.
Exploring some examples available online, I see code like this:
XElement xelement = XElement.Load("..\\..\\Employees.xml");
IEnumerable<XElement> employees = xelement.Elements();
Console.WriteLine("List of all Employee Names :");
foreach (var employee in employees)
{
Console.WriteLine(employee.Element("Name").Value);
}
This would seem to suffer from the exact same issue. If the "Name" element does not exist, Element() returns null and there is an error calling the Value property.
I need a number of blocks like the first code snippet above. Is there a simple way to have it work and not throw an exception if some data is missing?
You can use the combination of the explicit string conversion from XAttribute to string (which will return null if the operand is null) and the FirstOrDefault method:
var name = xelement.Elements("Employee")
.Select(x => (string) x.Attribute("name"))
.FirstOrDefault();
That will be null if either there's no such element (because the sequence will be empty, and FirstOrDefault() will return null) or there's an element without the attribute (in which case you'll get a sequence with a null element, which FirstOrDefault will return).
I often use extension methods in cases like this as they work even if the reference is null. I use a slightly modified version of the extension method's from Anders Abel's very good blog posting from early 2012 'Null Handling with Extension Methods':
public static class XElementExtension
{
public static string GetValueOrDefault(this XAttribute attribute,
string defaultValue = null)
{
return attribute == null ? defaultValue : attribute.Value;
}
public static string GetAttributeValueOrDefault(this XElement element,
string attributeName,
string defaultValue = null)
{
return element == null ? defaultValue : element.Attribut(attributeName)
.GetValueOrDefault(defaultValue);
}
}
If you want to return 'null' if the element or attribute doesn't exist:
var name = xelement.Element("Employee")
.GetAttributeValueOrDefault("name" );
If you want to return a default value if the element or attribute doesn't exist:
var name = xelement.Element("Employee")
.GetAttributeValueOrDefault("name","this is the default value");
To use in your for loop:
XElement xelement = XElement.Load("..\\..\\Employees.xml");
IEnumerable<XElement> employees = xelement.Elements();
Console.WriteLine("List of all Employee Names :");
foreach (var employee in employees)
{
Console.WriteLine(employee.GetAttributeValueOrDefault("Name"));
}
You could always use XPath:
string name = xelement.XPathEvaluate("string(Employee/#name)") as string;
This will be either the value of the attribute, or null if either Employee or #name do not exist.
And for the iterative example:
foreach (XNode item in (IEnumerable)xelement.XPathEvaluate("Employee/Name"))
{
Console.WriteLine(item.Value);
}
XPathEvaluate() will only select valid nodes here, so you can be assured that item will always be non-null.
It all depends on what you want to do with the data once you've extracted it from the XML.
You would do well to look at languages that are designed for XML processing, such as XSLT and XQuery, rather than using languages like C#, which aren't (though Linq gives you something of a hybrid). Using C# or Java you're always going to have to do a lot of work to cope with the fact that XML is so flexible.
Use the native XmlReader. If your problem is reading large XML files instead of allowing the XElement to build an object representation, you can build something like Java SAX parser that only stream the XML.
Ex:
http://www.codeguru.com/csharp/csharp/cs_data/xml/article.php/c4221/Writing-XML-SAX-Parsers-in-C.htm