Get the XElement for the XML - c#

Here's my XML File:
<Applications>
<Application Name="Abc">
<Section Name="xyz">
<Template Name="hello">
...
....
</Template>
</Section>
</Application>
<Application Name="Abc1">
<Section Name="xyz1">
<Template Name="hello">
...
....
</Template>
</Section>
</Application>
What I need to do is get the Template XElement from the given structure based upon the Name attribute of Template tag. The problem is there can be multiple template tags with same attribute Name. The Distinguishing factor is Application Name attribute value and section attribute value.
Currently I'm able to get the XElement by first getting Application Element based upon it's attribute, then Section based upon it's attribute and then finally template based upon it' name.
I wanted to know if there is a way to get it in one go.

I would use the fact that you can call Elements or an existing sequence, so:
var template = doc.Descendants("Application")
.Where(x => (string) x.Attribute("Name") == applicationName)
.Elements("Section")
.Where(x => (string) x.Attribute("Name") == sectionName)
.Elements("Template")
.Where(x => (string) x.Attribute("Name") == templateName)
.FirstOrDefault();
You might even want to add an extension method somewhere:
public static IEnumerable<XElement> WithName(this IEnumerable<XElement> elements,
string name)
{
this elements.Where(x => (string) x.Attribute("Name") == name);
}
Then you can rewrite the query as:
var template = doc.Descendants("Application").WithName(applicationName)
.Elements("Section").WithName(sectionName)
.Elements("Template").WithName(templateName)
.FirstOrDefault();
... which I think you'll agree is quite readable :)
Note that the use of casting XAttribute to string instead of using the Value property means that any elements without the Name attribute are just effectively ignored rather than causing a NullReferenceException.

The following code should do the trick:
var template = doc.Descendants("Template")
.Where(x => x.Attribute("Name").Value == "hello"
&& x.Parent.Attribute("Name").Value == "xyz1"
&& x.Parent.Parent.Attribute("Name").Value == "Abc1");
Please note that this code throws exceptions if the XML doesn't conform to the specification. Specifically, there will be a NullReferenceException if any of the tags in question don't contain an attribute named "Name". Or if the Template tag doesn't have two levels of parents.

XDocument doc = XDocument.Load("Path of xml");
var selection =
doc.Descendants("Section").Select(item => item).Where(
item => item.Attribute("Name").Value.ToString().Equals("Section Name Value")).ToList();
if(null != selection)
{
var template =
selection.Descendants("Template").Select(item => item).Where(
item => item.Attribute("Name").Value.ToString().Equals("Template name value"));
}

XPath should help you. Use the Extensions.XPathSelectElement Method (XNode, String) :
XDocument xdoc = XDocument.Load("yourfile.xml");
string xPathQuery = string.Format(
"/Applications/Application[#Name='{0}']/Section[#Name='{1}']/Template[#Name='{2}']",
"MyApplication",
"MySection",
"MyTemplate"
);
XElement template = xdoc.Root.XPathSelectElement(xPathQuery);

Related

Selecting a child node having specific value

I want to check that if the "< city>" node 'having a specific value (say Pathankot )' already exist in the xml file under the a particular "< user Id="any"> having a specific Id", before inserting a new city node into the xml.
< users>
< user Id="4/28/2015 11:29:44 PM">
<city>Fazilka</city>
<city>Pathankot </city>
<city>Jalandher</city>
<city>Amritsar</city>
</user>
</users>
In order to insert I am using the Following c# code
XDocument xmlDocument = XDocument.Load(#"C:\Users\Ajax\Documents\UserSelectedCity.xml");
string usrCookieId = Request.Cookies["CookieId"].Value;
xmlDocument.Element("Users")
.Elements("user")
.Single(x => (string)x.Attribute("Id") == usrCookieId)
//Incomplete because same named cities can be entered more that once
//need to make delete function
.Add(
new XElement("city", drpWhereToGo.SelectedValue));
My Questions:
How Can i check weather the < city> node having specific value say
Pathankot already exist in the xml file Before Inserting a new city
node.
I am Using absolute Path in" XDocument xmlDocument =
XDocument.Load(#"C:\Users\Ajax\Documents\Visual Studio
2012\WebSites\punjabtourism.com\UserSelectedCity.xml");" This
does not allow me to move the files to new folder without changing
the path which is not desirable. But if i use the relative path The
Error Occures "Access Denied";
I would use this simple approach:
var query =
xmlDocument
.Root
.Elements("user")
.Where(x => x.Attribute("Id").Value == usrCookieId)
.Where(x => !x.Elements("city").Any(y => y.Value == "Pathankot"));
foreach (var xe in query)
{
xe.Add(new XElement("city", drpWhereToGo.SelectedValue));
}
It's best to avoid using .Single(...) or .First(...) if possible. The description of your problem doesn't sound like you need to use these though.
Try this:-
First load the XML file into XDocument object by specifying the physical path where your XML file is present. Once you have the object just take the First node with matching condition (please note I am using First instead of Single cz you may have multiple nodes with same matching condition, Please see the Difference between Single & First)
XDocument xmlDocument = XDocument.Load(#"YourPhysicalPath");
xmlDocument.Descendants("user").First(x => (string)x.Attribute("Id") == "1"
&& x.Elements("city").Any(z => z.Value.Trim() == "Pathankot"))
.Add(new XElement("city", drpWhereToGo.SelectedValue));
xmlDocument.Save("YourPhysicalPath");
Finally add the required city to the node retrieved from the query and save the XDocument object.
Update:
If you want to check first if all the criteria fulfills then simply use Any like this:-
bool isCityPresent = xdoc.Descendants("user").Any(x => (string)x.Attribute("Id") == "1"
&& x.Elements("city").Any(z => z.Value.Trim() == "Pathankot"));
I'd create an extension to clean it up a bit, and use XPath for the search.
public static class MyXDocumentExtensions
{
public static bool CityExists(this XDocument doc, string cityName)
{
//Contains
//var matchingElements = doc.XPathSelectElements(string.Format("//city[contains(text(), '{0}')]", cityName));
//Equals
var matchingElements = doc.XPathSelectElements(string.Format("//city[text() = '{0}']", cityName));
return matchingElements.Count() > 0;
}
}
And call it like that:
XDocument xmlDocument = XDocument.Load("xml.txt");
var exists = xmlDocument.CityExists("Amritsar");
Expanding on your question in the comment, you can then use it as:
if(!xmlDocument.CityExists("Amritsar"))
{
//insert city
}
If you would like to match regardless of the trailing whitespace in the XML, you can wrap the text() call in XPath with a normalize-space:
var matchingElements = doc.XPathSelectElements(string.Format("//city[normalize-space(text()) = '{0}']", cityName.Trim()));

How can I insert an XElement into an existing xml-file?

I do have following xml:
<Assembly>
<Bench>
<Typ>P1</Typ>
<DUT>
<A>6</A>
</DUT>
</Bench>
<Bench>
<Typ>P2</Typ>
<DUT>
<A>6</A>
</DUT>
</Bench>
</Assembly>
How can I get a reference to 'P2'-element that I can insert a new DUT? I tried following code which gives me an error:
var xElement = xmlDoc.Element("Assembly")
.Elements("Bench")
.Where(item => item.Attribute("Typ").Value == "P2")
.FirstOrDefault();
xElement.AddAfterSelf(new XElement("DUT"));
thanks in advance
Typ is element name, not an attribute. If you meant to add new <DUT> element after existing <DUT> under the second <Bench>, this slight change to the code you've tried should work :
var xElement = xmlDoc.Element("Assembly")
.Elements("Bench")
.FirstOrDefault(item => item.Element("Typ").Value == "P2");
xElement.AddAfterSelf(new XElement("DUT"));
Another way of doing the same thing, just to show the options available.
XElement typ = xmlDoc.Descentants("Typ")
.FirstOrDefault(typ => ((string)typ) == "P2");
You can use the same AddAfterSelf as har07, or .Parent.Add() if it doesn't matter where in the parent Bench it goes. Add will add it as the last element.
typ.Parent.Add(new XElement("DUT"));

Reading XML nested node values

Having an issue grabbing values in an XML file
The structure is as followed
<configuration>
<settings>
<add key="folder" value = "c:\...." />
</settings>
</configuration>
i want to be able to read the value from folder.
string val = string.Empty;
foreach (XElement element in XElement.Load(file).Elements("configuration"))
{
foreach (XElement element2 in element.Elements("settings"))
{
if (element2.Name.Equals("folder"))
{
val = element2.Attribute(key).Value;
break;
}
}
}
return val;
The name of the element isn't folder... that's the value of the key attribute. Also note that as you've used XElement.Load, the element is the configuration element - asking for Elements("configuration") will give you an empty collection. You could either load an XDocument instead, or just assume you're on a configuration element and look beneath it for settings.
I think you want:
return XElement.Load(file)
.Elements("settings")
.Elements("add")
.Where(x => (string) x.Attribute("key") == "folder")
.Select(x => (string) x.Attribute("value"))
.FirstOrDefault();
You can use XPath:
var folder = XElement.Load(file)
.XPathSelectElements("/settings/add[#key='folder']")
.Select(a => (string)a.Attribute("value"))
.FirstOrDefault();

access xml element by attribute value

Probably this question repeated, but i am not satiesfied with existing answers. I want to get xml element from dynamically generated xml file by attribute value. we don't know how many nodes, and its herarchy. but each element, its sub element, its sub-sub elements, sub-sub-sub elements...so on will contain unique guid as "Id" attribute :
<Element id="">
<SubElement id=""></SubElement>
<SubElement id="">
<SubSubElement id="">
<SubSubSubElement id="">
<SubSubSubSubElement id="">....other sub inside this ...</SubSubSubSubElement>
</SubSubSubElement>
</SubSubElement>
</SubElement>
</Element>
I want to find the element by only passing the Guid value. nonethless of its xpath, its node location / position. how can i do this in C#? is i need to use LINQ?
Edited:
XDocument xmldoc = XDocument.Load(xmlFilePath);
XElement selectedElement = xmldoc.Descendants().Where(x => (string) x.Attribute("id") == myIdvalue).FirstOrDefault();
Exception :
"Expression cannot contain lambda expressions"
I have added Using System.Linq namspaces.
hoipolloi has given an XPath answer, which is fine - but I would personally use LINQ to XML. (See my blog post on code and data for reasons.)
var element = parent.Descendants()
.Where(x => (Guid?) x.Attribute("id") == id)
.FirstOrDefault();
This will perform appropriate GUID parsing on each id attribute (returning a "null" Guid? value for non-GUIDs). If you're certain of the text format of your ID, you can cast to string instead:
var element = parent.Descendants()
.Where(x => (string) x.Attribute("id") == idText)
.FirstOrDefault();
Change the FirstOrDefault to Single, SingleOrDefault or First depending on your requirements.
EDIT: It's not at all clear what's going wrong with the code you've posted. Here's a short but complete program which shows it working fine. Please compare this with your code:
using System;
using System.Linq;
using System.Xml.Linq;
class Test
{
static void Main()
{
string xml = "<parent><foo id='bar' /><foo id='baz' /></parent>";
XDocument doc = XDocument.Parse(xml);
string idToFind = "bar";
XElement selectedElement = doc.Descendants()
.Where(x => (string) x.Attribute("id") == idToFind).FirstOrDefault();
Console.WriteLine(selectedElement);
}
}
You can use XPath to do this. For instance, the following matches all elements with an id of 'foo', irrespective of their location in the document:
//*[#id='foo']

LINQ Statement WHERE Question

I am returning a list. This is contains the names of xml nodes that cannot be blank in my XML file.
List<Setting> settingList = SettingsGateway.GetBySettingTypeList("VerifyField");
I have a LINQ Statement. I am trying to return all transactions that have empty nodes. The list here is returning the nodes that CANNOT be empty. Does anyone know what I am doing wrong here?
The following code is supposed to Bind the "transactions" to a DataGrid and display the Txn's that have empty nodes which are required.
var transactionList =
from transactions in root.Elements(XName.Get("Transactions")).Elements().AsEnumerable()
where transactions.Elements().Any
(
el =>
//String.IsNullOrEmpty(el.Value) &&
//elementsThatCannotBeEmpty.Contains(el.Name)
settingList.Any(
name => String.IsNullOrEmpty(el.Element(name.SettingValue).Value)
)
)
select new
{
CustomerName = transactions.Element(XName.Get("CustomerName")).Value,
ConfirmationNumber = transactions.Element(XName.Get("ConfirmationNumber")).Value
};
GridView.DataSource = transactionList;
GridView.DataBind();
XML File Example:
<OnlineBanking>
<Transactions>
<Txn>
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2></CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
</Transactions>
</OnlineBanking>
Okay, first problem: if the element is missing, you'll get a NullReferenceException.
I'd suggest creating a List<string> of the elements which can't be null, to make the query simple. Then:
var requiredElements = settingList.Select(x => x.SettingValue).ToList();
var transactionList = root
.Elements("Transactions")
.Elements("Txn")
.Where(x => requiredElements
.Any(name => string.IsNullOrEmpty((string) x.Element(name)));
I think that should be okay, and slightly simpler than your original code... but to be honest, your original code looks like it should have worked anyway. What did it actually do? You haven't been very clear about the actual results versus the expected ones...
Something like this:
var transactionList =
root
.Elements(XName.Get("Transactions")) //Get <Transaction> elements
.Elements() //Get <Txn> elements
.Where(txn => txn.Elements().Any(e => e.Value == String.Empty)) //Filter <Txn> Elements if it have any element like this: <CustomerStreet2></CustomerStreet2>
.Select(x => new {
PropertyX = x.Element(XName.Get("UserName")),
PropertyY = x.Element(XName.Get("CustomerStreet")),
...
});
Works with:
<OnlineBanking>
<Transactions>
<Txn> <!-- This one matches! -->
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2></CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
<Txn> <!-- This one doesn't match! -->
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2>ASDASD</CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
</Transactions>
</OnlineBanking>

Categories