Given the following XML:
<enrollment>
<school>
<students>
<studentA fname="John" lname="Doe" age="23" />
<studentB fname="Mary" lname="Johnson" age="22" />
</students>
</school>
</enrollment>
and here's my code to iterate the attributes-
foreach(XmlAttribute attr in node.Attributes)
{
//--get the XPath for each attribute
}
Where node = "studentA", how do I get the XPath for each attribute?
EDIT:
Basically what I'm trying to achieve here is to compare if two nodes are the same. So I have to check if they have the same name, attributes, and attribute values. Therefore given a node, I need an xpath expression that that matches the conditions stated.
you can directly put it like as for all the studentA nodes-
Xpath- "//studentA"
or to iterate over a specific node-
Xpath- "enrollment/school/students/studentA"
In case you want to find attribute fname
Xpath- "enrollment/school/students/studentA[#fname]"
Assuming myXml is your xmlDocument
you can iterate over a particular node attributes like-
System.Xml.XmlNode xn = myXml.SelectSingleNode("enrollment/school/students/studentA");
foreach (System.Xml.XmlAttribute attrib in xn.Attributes)
{
// find attribute name using attrib.Name
string sAttribName = attrib.Name;
if (sAttribName == "fname")
{
//Check your codes here
}
}
You can use enrollment/school/students/studentA/#fname
#attribute is for attribute selection.
Related
I want to find the nodes which has a specific attribute (like in example below we have attribute attr )
<root>
<anynode id="1" attr="abc">
first node
</anynode>
<anynode id="2">
2nd node
</anynode>
<anynode id="3" attr="abc">
3rd node
</anynode>
<anynode id="4" attr="def">
4th node
</anynode>
</root>
and i want it to traverse with XMLDocument and XDocument both
and if i can get value of ID and ATTR in one ittration that would be great
i have googled little bit but i didnt find any path which can help me lead to
final result i want
1,abc , first node
3,abc , 3rd node
4,def , 4th node
any help will be appreciated
Using the System.Xml.Linq namespace we can take advantage of the XDocument class to find the "id" and "attr" attributes of the "anynode" element.
var xmlDocument = XDocument.Load(#"path to xml file");
var nodes = xmlDocument.Element("root")?.Elements("anynode");
if (nodes == null)
return;
foreach (var node in nodes)
{
var id = node.Attribute("id")?.Value;
var attr = node.Attribute("attr")?.Value;
Console.WriteLine($"ID = {id}, ATTR = {attr}");
}
I'm trying to obtain the relevant XML attribute based on the value but I cannot get it to work.
What I am trying to achieve is based on the returned value I want to output the elements name.
Where am I going wrong?
Here's my code so far:
XML:
<addresses>
<address name="house 1">No 1</ipaddress>
<address name="house 2">Flat 3</ipaddress>
<address name="house 3">Siccamore Drive</ipaddress>
</addresses>
C#:
string configPath = _AppPath + #"\HouseAddresses.xml";
XDocument addressXdoc = XDocument.Load(configPath);
XElement addressXmlList = addressXdoc.Element("traplistener");
foreach (XNode node in addressXmlLst.Element("addresses").Descendants())
{
PropertyList = ("string")node.Attribute("name");
}
The XNode type can be seen as a "base". As the documentation states, it represents the abstract concept of a node (element, comment, document type, processing instruction, or text node) in the XML tree. Adding a Attribute property to a text, for example, does not really make sense in the XML context. For that reason, the XNode type does not provide a Attribute property. The XElement type, however, does. Therefore, changing your foreach loop to the version bellow, should do the trick:
foreach (XElement element in addressXmlLst.Element("addresses").Descendants())
{
PropertyList = ("string")element.Attribute("name");
}
A "random" note on your code: since XElement extends XNode the elements returned by Descendants() are correctly converted; for this reason, your problem appears to come from the fact that XNode does not expose a Attribute property, when, in fact, it originates from an unnecessary type conversion.
As an improvement, I would suggest the following:
foreach (XElement element in addressXmlLst.Element("addresses").Descendants())
{
//check if the attribute is really there
//in order to prevent a "NullPointerException"
if (element.Attribute("name") != null)
{
PropertyList = element.Attribute("name").Value;
}
}
In addition to Andrei's answer you can also convert the xml to a dictionary directly via LINQ:
var dictionary = addressXmlLst.Element("addresses").Descendants()
.ToDictionary(
element => element.Attribute("name").Value, // the key
element => element.Value // the value
);
This is my XML:
<?xml version="1.0"?>
<formatlist>
<format>
<formatName>WHC format</formatName>
<delCol>ID</delCol>
<delCol>CDRID</delCol>
<delCol>TGIN</delCol>
<delCol>IPIn</delCol>
<delCol>TGOUT</delCol>
<delCol>IPOut</delCol>
<srcNum>SRCNum</srcNum>
<distNum>DSTNum</distNum>
<connectTime>ConnectTime</connectTime>
<duration>Duration</duration>
</format>
<format>
<formatName existCombineCol="1">Umobile format</formatName> //this format
<delCol>billing_operator</delCol>
<hideCol>event_start_date</hideCol>
<hideCol>event_start_time</hideCol>
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>ConnectdateTimeAFcombine</name>
<combineDate>event_start_date</combineDate>
<combineTime>event_start_time</combineTime>
</afCombineName>
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>aaa</name>
<combineDate>bbb</combineDate>
<combineTime>ccc</combineTime>
</afCombineName>
<modifyPerfixCol action="add" perfix="60">bnum</modifyPerfixCol>
<srcNum>anum</srcNum>
<distNum>bnum</distNum>
<connectTime>ConnectdateTimeAFcombine</connectTime>
<duration>event_duration</duration>
</format>
</formatlist>
I want to find format with Umobile format then iterate over those two nodes.
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>ConnectdateTimeAFcombine</name>
<combineDate>event_start_date</combineDate>
<combineTime>event_start_time</combineTime>
</afCombineName>
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>aaa</name>
<combineDate>bbb</combineDate>
<combineTime>ccc</combineTime>
</afCombineName>
and list all the two node's child nodes. The result should like this:
ConnectdateTimeAFcombine,event_start_date,event_start_time.
aaa,bbb,ccc
How can I do this?
foreach(var children in format.Descendants())
{
//Do something with the child nodes of format.
}
For all XML related traversing, you should get used to using XPath expressions. It is very useful. Even if you could perhaps do something easier in your specific case, it is good practice to use XPath. This way, if your scheme changes at some point, you just update your XPath expression and your code will be up and running.
For a complete example, you can have a look at this article.
You can use the System.Xml namespace APIs along with System.Xml.XPath namespace API. Here is a quick algorithm that will help you do your task:
Fetch the text node containing the string Umobile format using the below XPATH:
XmlNode umobileFormatNameNode = document.SelectSingleNode("//formatName[text()='Umobile format']");
Now the parent of umobileFormatNameNode will be the node that you are interested in:
XmlNode formatNode = umobileFormatNameNode.ParentNode;
Now get the children for this node:
XmlNodeList afCombineFormatNodes = formatNode.SelectNodes("afCombineName");
You can now process the list of afCombineFormatNodes
for(XmlNode xmlNode in afCombineNameFormtNodes)
{
//process nodes
}
This way you can access those elements:
var doc = System.Xml.Linq.XDocument.Load("PATH TO YOUR XML FILE");
var result = doc.Descendants("format")
.Where(x => (string)x.Element("formatName") == "Umobile format")
.Select(x => x.Element("afCombineName"));
Then you can iterate the result this way:
foreach (var item in result)
{
string format = item.Attribute("format").Value.ToString();
string name = item.Element("name").Value.ToString();
string combineDate = item.Element("combineDate").Value.ToString();
string combineTime = item.Element("combineTime").Value.ToString();
}
I'm trying to traverse an XML document and select certain node attributes. The XML is dynamically generated.
<?xml version="1.0" encoding="ISO-8859-1"?>
<streams>
<stream>
<title>+23 (Panama)</title>
<info resolution="768x420" bitrate="1000kbps"/> ----- Need These
<swfUrl>http://www.freeetv.com/script/mediaplayer/player.swf</swfUrl>
<link>rtmp://200.75.216.156/live/</link>
<pageUrl>http://www.freeetv.com/</pageUrl>
<playpath>livestream</playpath>
<language>Music</language>
<advanced></advanced>
</stream>
</streams>
The code that I'm trying to use with zero luck and Visual Studio saying "No you're wrong. Try 600 more times" is
xDoc.Load("http://127.0.0.1/www/xml.php");
XmlNodeList nodes = xDoc.SelectNodes("/streams/stream");
foreach (XmlNode xn in nodes)
{
ListViewItem lvi = listView1.Items.Add(xn["title"].InnerText);
lvi.SubItems.Add(xn["swfUrl"].InnerText);
lvi.SubItems.Add(xn["link"].InnerText);
lvi.SubItems.Add(xn["pageUrl"].InnerText);
lvi.SubItems.Add(xn["playpath"].InnerText);
lvi.SubItems.Add(xn["language"].InnerText);
lvi.SubItems.Add(xn["advanced"].InnerText);
lvi.SubItems.Add(xn["//info/#resolution"].Value);
}
Please tell me oh wise ones what am I doing wrong?
If you want to select node's attribute using XPath you should use SelectSingleNode method, e.g.:
xn.SelectSingleNode("info/#resolution").Value
To select resolution attribute of your last node you need to use:
xn["info"].Attributes["resolution"].Value
Alternatively, you can try LINQ to XML for the same results (I find its API easier to use):
var doc = XDocument.Parse("http://127.0.0.1/www/xml.php");
foreach (var d in doc.Descendants("stream"))
{
ListViewItem lvi = listView1.Items.Add(d.Element("title").Value);
lvi.SubItems.Add(d.Element("swfUrl").Value);
// ...
vi.SubItems.Add(d.Element("info").Attribute("resolution").Value);
}
Here is an example of LINQ to XML to extract attributes from the entire document of a particular attribute name OR list of attribute names.
var xml = XElement.Parse("http://127.0.0.1/www/xml.php");
// find all attributes of a given name
var attributes = xml
.Descendants()
.Attributes("AttributeName")
// find all attributes of multiple names
var attributes = xml
.Descendants()
.Attributes()
.Where(a => ListOfAttribNames.Contains(a.Name.LocalName))
Replace:
lvi.SubItems.Add(xn["//info/#resolution"].Value);
with:
lvi.SubItems.Add(xn.SelectSingleNode("info/#resolution").Value);
I have a simple XML
<AllBands>
<Band>
<Beatles ID="1234" started="1962">greatest Band<![CDATA[lalala]]></Beatles>
<Last>1</Last>
<Salary>2</Salary>
</Band>
<Band>
<Doors ID="222" started="1968">regular Band<![CDATA[lalala]]></Doors>
<Last>1</Last>
<Salary>2</Salary>
</Band>
</AllBands>
However ,
when I want to reach the "Doors band" and to change its ID :
using (var stream = new StringReader(result))
{
XDocument xmlFile = XDocument.Load(stream);
var query = from c in xmlFile.Elements("Band")
select c;
...
query has no results
But
If I write xmlFile.Elements().Elements("Band") so it Does find it.
What is the problem ?
Is the full path from the Root needed ?
And if so , Why did it work without specify AllBands ?
Does the XDocument Navigation require me to know the full level structure down to the required element ?
Elements() will only check direct children - which in the first case is the root element, in the second case children of the root element, hence you get a match in the second case. If you just want any matching descendant use Descendants() instead:
var query = from c in xmlFile.Descendants("Band") select c;
Also I would suggest you re-structure your Xml: The band name should be an attribute or element value, not the element name itself - this makes querying (and schema validation for that matter) much harder, i.e. something like this:
<Band>
<BandProperties Name ="Doors" ID="222" started="1968" />
<Description>regular Band<![CDATA[lalala]]></Description>
<Last>1</Last>
<Salary>2</Salary>
</Band>
You can do it this way:
xml.Descendants().SingleOrDefault(p => p.Name.LocalName == "Name of the node to find")
where xml is a XDocument.
Be aware that the property Name returns an object that has a LocalName and a Namespace. That's why you have to use Name.LocalName if you want to compare by name.
You should use Root to refer to the root element:
xmlFile.Root.Elements("Band")
If you want to find elements anywhere in the document use Descendants instead:
xmlFile.Descendants("Band")
The problem is that Elements only takes the direct child elements of whatever you call it on. If you want all descendants, use the Descendants method:
var query = from c in xmlFile.Descendants("Band")
My experience when working with large & complicated XML files is that sometimes neither Elements nor Descendants seem to work in retrieving a specific Element (and I still do not know why).
In such cases, I found that a much safer option is to manually search for the Element, as described by the following MSDN post:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/3d457c3b-292c-49e1-9fd4-9b6a950f9010/how-to-get-tag-name-of-xml-by-using-xdocument?forum=csharpgeneral
In short, you can create a GetElement function:
private XElement GetElement(XDocument doc,string elementName)
{
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
return element;
}
}
return null;
}
Which you can then call like this:
XElement element = GetElement(doc,"Band");
Note that this will return null if no matching element is found.
The Elements() method returns an IEnumerable<XElement> containing all child elements of the current node. For an XDocument, that collection only contains the Root element. Therefore the following is required:
var query = from c in xmlFile.Root.Elements("Band")
select c;
Sebastian's answer was the only answer that worked for me while examining a xaml document. If, like me, you'd like a list of all the elements then the method would look a lot like Sebastian's answer above but just returning a list...
private static List<XElement> GetElements(XDocument doc, string elementName)
{
List<XElement> elements = new List<XElement>();
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
elements.Add(element);
}
}
return elements;
}
Call it thus:
var elements = GetElements(xamlFile, "Band");
or in the case of my xaml doc where I wanted all the TextBlocks, call it thus:
var elements = GetElements(xamlFile, "TextBlock");