access xml element by attribute value - c#

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']

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()));

Get a xml element with specific attribute value in c#

I need to get a value of a SubTopic element which has an attribute called "Name" with specific value. I do it this way;
IEnumerable<XElement> list =
(from el in xdoc.Elements()
where (string)el.Attribute("Name") == "creatingTests"
select el);
The collection has zero elements.
I tried putting xdoc.Elements("SubTopic") instead of empty parameter, but with no success.
My XML file structure;
<?xml version="1.0" encoding="windows-1250" ?>
<Help Title="TestTool - tematy pomocy">
<Topic Name="creatingTests" Title="Tworzenie testów">
<SubTopic Name="saveload" Title="Zapis i odczyt z pliku">
Content
</SubTopic>
</Topic>
</Help>
How can I get that value of Help/Topic(Name="creatingTests")?
xdoc is of course XDocument object with loaded xml and it does have the content of my file.
xdoc.Elements() returns only one element - the Root of XML tree (it's <Help> element in your example.
Change your query to:
IEnumerable<XElement> list =
(from el in xdoc.Root.Elements()
where (string)el.Attribute("Name") == "creatingTests"
select el);
It returns collection with one element. Use First or FirstOrDefault to get it as single item, not a collection:
XElement item = (from el in xdoc.Root.Elements()
where (string)el.Attribute("Name") == "creatingTests"
select el).FirstOrDefault();
Here's an alternative by using System.Xml.XPath:
using System.Xml.Linq;
using System.Xml.XPath;
class Program
{
static void Main(string[] args)
{
var xdoc = XDocument.Load("input.xml");
var subTopic = xdoc
.XPathSelectElement("//Topic[#Name='creatingTests']/SubTopic");
}
}
Very easy and simplest way is to use XSLT..
1.Create an XSLT Template.
2.Call it in c#.
xmlDaynamic.DocumentContent = "Your XML Input";
xmlDaynamic.TransformSource = "YourTemplate with extension";
3.Your task is done.
4.xmlDaynamic is a server control.
Try using XPATH
http://support.microsoft.com/kb/308333
"//Topic[#Name='creatingTests']"

Parsing xml string to get certain tag values within

I have an xml string and have different records within and i want to extract the id within each record. Here is a sample of the xml:
<UploadsInformation >
<Record>
<TaskGUID>48A583CA-A532-419A-9CDB-292764CEC541</TaskGUID>
</Record>
<Record>
<TaskGUID>ED6BA682-2BB2-4ADF-8355-9C605E16E088</TaskGUID>
</Record>
<Record>
<TaskGUID>D20D7042-FC5B-4CF7-9496-D2D9DB68CF52</TaskGUID>
</Record>
<Record>
<TaskGUID>F5DB10C5-D517-4CDA-8AAA-4E3F50B5FF3C</TaskGUID>
</Record>
</UploadsInformation>
This is what i have as a string to extract the information that i need but not sure if it correct or not because when i debug the string seems to be the xml file and not just the specified guid.
string data = new XDocument(new XElement("Record",
uploads.Select(guid => new XElement("TaskGUID", guid.ToString()))))
.ToString();
uploads is: List<Guid?> uploads
If I understand your question correctly, you want to extract the Guids from the source XML, which you indicate is a string.
You can create an XDocument from a string with the following command:
XDocument doc = XDocument.Parse(xmlString);
XNamespace ns = "http://schemas.acatar.com/2013/03/Malt.Models";
List<string> uploads = doc.Descendants(ns + "TaskGUID")
.Select(x => x.Value).ToList();
string uploadString = String.Join(",", uploads);
I used XNamespace because there is a namespace (two, actually) defined in the XML, and unless you prefix the correct one to the element name you won't get any results.
You might be able to combine the last two steps into one line, but I'm not 100% sure.
The above code was tested with your example, and produces the following value for uploadString:
48A583CA-A532-419A-9CDB-292764CEC541,ED6BA682-2BB2-4ADF-8355-9C605E16E088,D20D7042-FC5B-4CF7-9496-D2D9DB68CF52,F5DB10C5-D517-4CDA-8AAA-4E3F50B5FF3C
However, if you're going to loop through the result and pass each one in singularly to a stored procedure, I'd skip the String.Join and just loop through the List:
foreach (string id in uploads)
{
// Do your stored procedure call for each Guid.
}
Added in Response to Comment
In the situation in your comment, if you have a List that you want to get the values for, you'd do essentially the same, but you'll need to check for nulls and (probably) convert the Guid to a string before passing it into the stored proc:
foreach (Guid? g in uploads)
{
if (g != null)
{
string newGuid = g.ToString();
// do your data access stuff here
}
}
You can't use local names of elements, because you have namespace declared. So, you should use namespace to provide names:
XNamespace ns = "http://schemas.acatar.com/2013/03/Malt.Models";
var guids = from r in xdoc.Root.Elements(ns + "Record")
select Guid.Parse((string)r.Element(ns + "TaskGUID"));
Or query your xml without specifying names of elements:
var guids = xdoc.Root.Elements()
.Select(r => Guid.Parse((string)r.Elements().Single()));
I think this is either what you are after or perhaps might shed some light on the direction to go:
string xml = ""; // XML data here
XDocument doc = XDocument.Parse(xml);
List<Guid> guids = doc.Descendants("TaskGUID")
.Select(g => new Guid(g.Value))
.ToList();

Get the XElement for the XML

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);

C# XmlElement: SelectSingleNode returns null for empty string?

I'm new to C#, and just started using XmlElement and its SelectSingleNode method. In my XML file there's a tag that may have a value (i.e. <tag>value</tag>) or be empty (i.e. <tag></tag>). If it's empty, SelectSingleNode returns null.
I'm currently using the following code to catch the value of the tag:
XmlElement elem = ....
string s = elem.SelectSingleNode("somepath").Value;
This code obviously raises an exception for empty tags. However, for me an empty tag is a valid value, where I expect the value of my string to be "".
Wrapping each call to SelectSingleNode with try...catch seems a huge waste of code (I have many fields that may be empty), and I'm sure there's a better way to achieve this.
What is the recommended approach?
EDIT:
Following requests, a sample XML code will be:
<Elements>
<Element>
<Name>Value</Name>
<Type>Value</Type> <-- may be empty
<Color>Value</Color>
</Element>
<Element>
<Name>Value</Name>
<Type>Value</Type>
<Color>Value</Color>
</Element>
</Elements>
The CS code:
XmlDocument doc = new XmlDocument();
doc.Load("name.xml");
foreach (XmlElement elem in doc.SelectNodes("Elements/Element"))
{
myvalue = elem.SelectSingleNode("Type/text()").Value;
}
Your sample code:
myvalue = elem.SelectSingleNode("Type/text()").Value;
is where the problem is. The XPath expression you've used there doesn't mean "give me text of element Type". It means "give me all child text nodes of element Type". And an empty element doesn't have any child text nodes (a text node cannot be empty in XPath document model). If you want to get text value of the node, you should use:
myvalue = elem.SelectSingleNode("Type").InnerText;
The recommended approach would be to use .NET's new XML API (namely LINQ to XML).
Here is an example:
using System;
using System.Linq;
using System.Xml.Linq;
class Program
{
static void Main()
{
String xml = #"<Root><Value></Value></Root>";
var elements = XDocument.Parse(xml)
.Descendants("Value")
.Select(e => e.Value);
}
}
http://msdn.microsoft.com/en-us/library/system.xml.xmlnode.value(VS.71).aspx
Because the "value" returned depends on the NodeType, there is a chance that the node will be interpreted as a type that can return NULL.
You might be better off using:
XmlElement elem = ....
string s = elem.SelectSingleNode("somepath").InnerText;
as XMLNode.InnerText (or XmlNode.InnerXML) will return a string, including an empty string.
Maybe this will work for you:
string s = elem.SelectSingleNode("somepath") != null ? elem.SelectSingleNode("somepath").value : ""
When I'm actually bothering with XML DOM, you could write a helper method along the lines of:
static string NodeValue(XmlNode node, string defaultValue)
{
if (node != null)
return node.Value ?? defaultValue;
return defaultValue;
}
Then you can do the following if you're not sure your node will exist:
string s = NodeValue(elem.SelectSingleNode("Type"), String.Empty);
If keeps your code readable, especially if you're doing this for multiple elements.
All that being said, SelectSingleNode(..) does not return a null value if the tag is empty. The Value attribute will be null however. If you're just trying to work around that, this should do:
string s = elem.SelectSingleNode("Type").Value ?? String.Empty;
Edit: ah, you're using /text() to select the actual text node. You could just get rid of that part of the XPath, but the NodeValue method I supplied should still work (the "?? defaultValue" part is not needed in that case though).

Categories