Extract descendant LINQ to XML - c#

Struggling in vain to extract the value of the Status descendant from an XML file generated via the Azure REST API using XDocument (LINQ to XML). No issues extracting root elements using this method:
var hsname = xmldoc.Root.Element(ns + "ServiceName").Value;
Getting the descendants is proving to be a nightmare. Abbreviated XML file below - please help :-)
-<HostedService xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/windowsazure">
<Url>https://management.core.windows.net/subscriptionID/services/hostedservices/hostedservicename</Url>
<ServiceName><hostedservicename></ServiceName>
-<HostedServiceProperties>
<Description/>
<Location>South Central US</Location>
<Label>EEEEEEEEEEEEEEEEEE</Label>
</HostedServiceProperties>
-<Deployments>
-<Deployment>
<Name>DeploymentName</Name>
<DeploymentSlot>Production</DeploymentSlot>
<PrivateID>55555555555555555555</PrivateID>
<Status>Running</Status>

You haven't shown what you've tried... but I'd expect this to be fine:
string status = (string) xmldoc.Descendants(ns + "Status").FirstOrDefault();
That will give you a null value if there are no Status elements. You may want to use Single(), SingleOrDefault() etc depending on your requirements.
EDIT: Just to expand on the comment, you can make your code more robust in the face of other Status elements like this:
string status = (string) xmldoc.Descendants(ns + "HostedService")
.Descendants(ns + "ServiceName")
.Descendants(ns + "Deployments")
.Descendants(ns + "Deployment")
.Descendants(ns + "Status")
.FirstOrDefault();

Related

Linq to XML Descendants can't read part of xml

I have XML document like this
<document>
<indexroot>
<type>type</type>
<model>model</model>
</indexroot>
<root>
<model_type1>model type 1</model_type1>
<model_type2>model type 2</model_type2>
</root>
</document>
And a linq to xml code:
var elements = (from element in pdb.Descendants()
select new
{
type = (string)element.Element("type") ?? "-",
model= (string)element.Element("model") ?? "-",
model_type1= (string)element.Element("model_type1") ?? "-",
model_type2= (string)element.Element("model_type2") ?? "-"
}).FirstOrDefault();
I get type and a model variables, but it seems I can't reach model_type1 and model_type2, now I understand that this happens because indexroot and root tags, amd if I seperate those tags into diffrent linq to xml code blocks with Descendants("indexroot") and Descendants("root"), everything works fine, but I wan't them in one block, is it possible to achieve that, and how?
You need to navigate down the XML heirarchy for each element you are trying to extract.
This is because the Element method only looks at direct children, not all descendants of a node. From the documentation:
Gets the first (in document order) child element with the specified XName.
One implementation using just Linq-to-XML might be:
xml = "<document>" +
"<indexroot>" +
" <type>type</type>" +
" <model>model</model>" +
"</indexroot>" +
"<root>" +
" <model_type1>model type 1</model_type1>" +
" <model_type2>model type 2</model_type2>" +
"</root>" +
"</document>";
XDocument doc = XDocument.Parse(xml);
var newItem = (from element in doc.Descendants("document")
select new
{
Type = (string)element.Element("indexroot").Element("type") ?? "-",
Model = (string)element.Element("indexroot").Element("model") ?? "-",
ModelType1 = (string)element.Element("root").Element("model_type1") ?? "-",
ModelType2 = (string)element.Element("root").Element("model_type2") ?? "-",
}).FirstOrDefault();
Console.WriteLine(newItem);

.ReplaceWith() not replacing XElement

Within an xml document I have the following
...
<TablixMembers>
<TablixMember>
<Group Name="Details" />
</TablixMember>
</TablixMembers>
</TablixMember>
<TablixHeader Name="MyField3" />
<TablixHeader Name="MyField2" />
</TablixMembers>
</TablixMember>
I have a for loop that looks for each element with a field name ("MyField3, MyField2, ...)
for (int g = 0; g < remainingtotals; g++)
{
groupTotals.Reverse();
ff.Add(new XElement(ns + "TablixHeader"));
ff.Descendants(ns + "TablixHeader").Last().SetAttributeValue("Name", groupTotals[g]);
string nName = GenerateUniqueName("Nsted");
while (ff.Descendants(ns + "TablixHeader").Any(n => (string)n.Attribute("Name") == groupTotals[g]))
{
var newnode = ff.Descendants(ns + "TablixHeader").Where(n => (string)n.Attribute("Name") == groupTotals[g]).Single();
newnode.ReplaceWith(new XElement(tGroupHeader));
}
groupTotals.Reverse();
}
Within this loop I have a List<string> groupTotals which holds the names of the fields.
It may not need to be in a while loop (I added this for testing) I have a query that looks for the specific field
var newnode = ff.Descendants(ns + "TablixHeader").Where(n => (string)n.Attribute("Name") == groupTotals[g]).Single();
newnode.ReplaceWith(new XElement(tGroupHeader));
This sets newnode equal to:
<TablixHeader Name="MyField3" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition" />
On the next line I am trying to replace this node with a new xml element structure
newnode.ReplaceWith(new XElement(tGroupHeader));
tGroupHeader is a memorystream that contains a large xml Element template that should be inserted and replace the current tablix node of the selected attribute name.
When this code runs the node does not get replaced. In fact nothing happens, I've set break points on the node, no xml exception is thrown it just steps right through it. I assume I am either calling the method incorrectly or the selecting of a single node is what is causing it to not replace the element.
I also thought it may be due to the injection of a large xml structure but I have also tested this using just a single element such as:
newnode.ReplaceWith(new XElement(ns + "Size"));// ns is the xml namespace for the schema I am using
this also did not result in the element being replaced.
Can anyone provide some insight into what I may be doing wrong?
-cheers
I finally found a work around for this it may not be the best approach but it was reliable. From what I could see when I attempted to query the node the element would be located but it would not update I think this was due in part to my attempt to inject and update based upon a .Descendants() query all within one method (this may not be right but it was all I could determine).
The work around approach that I used was instead of trying to navigate the xml structure for an element at the time of creating the element I added an ID attribute with a specific name.
ff.Add(new XElement(ns + "TablixMember"));
ff.Descendants(ns + "TablixMember").Last().SetAttributeValue("Name", groupTotals[g]);
ff.Descendants(ns + "TablixMember").Last().SetAttributeValue("ID", groupedItemTotals[g].GroupPosition.ToString());
This would then insert an XElement such as
<TablixMembers>
<TablixMember>
<TablixHeader xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition">
<Size>1in</Size>
<CellContents>
<Textbox Name="GroupTotalPPPccc2973356bf4beaa92cb0409b37b8ef" ID="1">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<--end snip -->
Once I added the id it made querying for the node much simpler
var newnode = ff.Descendants(ns + "TablixMember").Where(n => (string)n.Attribute("ID") == groupedItemTotals[g].GroupPosition.ToString()).Single();
From here I could then updated the elements within the node using Replace(). ReplaceWith() SetAttribut() etc.
in this snippet I selected the node, removed all child elements and then injected a new structure
newnode.RemoveAll();
newnode.Add(new XElement(tGroupHeader),
new XElement(ns + "KeepWithGroup"));
newnode.Descendants(ns + "TablixHeader").Last().Descendants(ns + "Textbox").Last().SetAttributeValue("Name", nName);
Finally, since the ID attribute was not supported by the current schema I was using I performed some clean up to remove any unsupported attributes.
report.Descendants(ns + "TablixMember").Where(p => (string)p.Attribute("ID") != null).Attributes("ID").Remove();
report.Descendants(ns + "Textbox").Where(p => (string)p.Attribute("ID") != null).Attributes("ID").Remove();
I'd be interested to see other suggestions or ideas.
thanks

Getting the right node in Linq to XML

Im trying to parse an XML file containing all the uploaded videos on a certain channel. Im attempting to get tbe value of the URL attribute in one of the <media:content> nodes and put it in the ViewerLocation field. However there are several of them. My current code is this:
var videos = from xElem in xml.Descendants(atomNS + "entry")
select new YouTubeVideo()
{
Title = xElem.Element(atomNS + "title").Value,
Description = xElem.Element(atomNS + "content").Value,
DateUploaded = xElem.Element(atomNS + "published").Value,
ThumbnailLocation = xElem.Element(mediaNS + "group").Element(mediaNS + "content").Attribute("url").Value,
ViewerLocation = xElem.Element(mediaNS + "group").Element(mediaNS + "content").Attribute("url").Value
};
It gets me the first node in the XML for entry with the name <media:content> as you would expect. However, the first entry in the XML isn't what I want. I want the second.
Below is the relevant XML.
<!-- I currently get the value held in this node -->
<media:content
url='http://www.youtube.com/v/ZTUVgYoeN_b?f=gdata_standard...'
type='application/x-shockwave-flash' medium='video'
isDefault='true' expression='full' duration='215' yt:format='5'/>
<!-- What i actually want is this one -->
<media:content
url='rtsp://rtsp2.youtube.com/ChoLENy73bIAEQ1kgGDA==/0/0/0/video.3gp'
type='video/3gpp' medium='video'
expression='full' duration='215' yt:format='1'/>
<media:content
url='rtsp://rtsp2.youtube.com/ChoLENy73bIDRQ1kgGDA==/0/0/0/video.3gp'
type='video/3gpp' medium='video'
expression='full' duration='215' yt:format='6'/>
I want the second node because it has a type of 'video/3gpp'. How would I go about selecting that one? My logic would be
if attribute(type == "video/3gpp") get this value.
But i do not know how to express this in Linq.
Thanks,
Danny.
Probably something like;
where xElem.Element(atomNS + "content").Attribute("type").Value == "video/3gpp"
Edit: I didn't quite know how to expand and explain this one without assuming the OP had no knowledge of Linq. You want to make your original query;
from xElem in xml.Descendants(atomNS + "entry")
where xElem.Element(atomNS + "content").Attribute("type").Value == "video/3gpp"
select new YouTubeVideo() {
...
}
You can interrogate attributes of a node, just like you can look at the elements of the document. If there are multiple elements with that attribute, you could then (assuming you always want the first you find)..
( from xElem in xml.Descendants(atomNS + "entry")
where xElem.Element(atomNS + "content").Attribute("type").Value == "video/3gpp"
select new YouTubeVideo() {
...
}).First();
I changed the original post, as I believe the node you're querying is the Element(atomNS + "content"), not the top level xElem
Using XPath from this Xml Library (Just because I know how to use it) with associated Get methods:
string videoType = "video/3gpp";
XElement root = XElement.Load(file); // or .Parse(xmlstring)
var videos = root.XPath("//entry")
.Select(xElem => new YouTubeVideo()
{
Title = xElem.Get("title", "title"),
Description = xElem.Get("content", "content"),
DateUploaded = xElem.Get("published", "published"),
ThumbnailLocation = xElem.XGetElement("group/content[#type={0}]/url", "url", videoType),
ViewerLocation = xElem.XGetElement("group/content[#type={0}]/url", "url", videoType)
});
If the video type doesn't change, you can replace the XGetElement's with:
xElem.XGetElement("group/content[#type='video/3gpp']/url", "url")
Its a lot cleaner not having to specify namespaces using the library. There is the Microsoft's XPathSelectElements() and XPathSelectElement() you can look into, but they require you to specify the namespaces and don't have the nice Get methods imo. The caveat is that the library isn't a complete XPath implementation, but it does work with the above.

Reading XML / RSS in .Net using System.Data.DataSet?

I really have not worked much with XML and I would appreciate some help.
I am trying to read an RSS feed from WeatherBug
I found some sample code here:
http://geekswithblogs.net/thibbard/archive/2006/01/13/65764.aspx
Where they are using System.Data.DataSet to read the XML feed.
(They are using VBasic I am using VC#)
Here is a sample of the XML I am trying to read:
http://api.wxbug.net/getLiveCompactWeatherRSS.aspx?ACode=A5333948364&zipcode=80918&unittype=0&OutputType=1
In the code example they get the wind speed as follows:
_wind = DS.Tables("wind-speed").Rows(0).Item("wind-speed_Text")
How do I know what to specify for each of the index values? I think I understand how they determined to specify wind-speed but how did they determine how to specify wind-speed_Text?
I can see the values in my debugger using:
m_ds.Tables["wind-speed"].Rows[0].ItemArray
I can see 3 values. "MPH", "3" and 0.
MPH would be the units. "3" would be the speed. Not sure what 0 is.
And how would I access the value at aws:WebURL?
And I am only seeing 8 tables.But there are more values than that. By poking around with the debugger, I found those URLs in the Weather table along with the values for wind-direction and gust-direction.
I thought I had the whole table thing nailed down but there seems to be no rhyme nor reason to it.
Can anyone put me on the right path? Basically I just need to know how to figure out how to find stuff in the DataTables based on what I am seeing in the XML.
Thanks
Quick and easy:
XDocument doc = XDocument.Load("http://api.wxbug.net/getLiveCompactWeatherRSS.aspx?ACode=A5333948364&zipcode=80918&unittype=0&OutputType=1");
XNamespace ns = "http://www.aws.com/aws";
var webUrl = doc.Element(ns + "weather").Element(ns + "WebURL").Value;
var wind = doc.Element(ns + "weather").Element(ns + "wind-speed").Value;
You can also use LINQ:
XDocument doc = XDocument.Load("http://api.wxbug.net/getLiveCompactWeatherRSS.aspx?ACode=A5333948364&zipcode=80918&unittype=0&OutputType=1");
XNamespace ns = "http://www.aws.com/aws";
var v = from d in doc.Elements(ns + "weather") select new { WebUrl = d.Element(ns + "WebURL").Value, WindSpeed = d.Element(ns + "wind-speed").Value};
foreach (var c in v)
{
Console.WriteLine(c.WebUrl + "--" + c.WindSpeed);
}

C# Using XPATH to select specific element with known values, then delete

I am new to xpath in C# and I am trying to select an element that has two specific values. This is what the XML format looks like
<?xml version="1.0" encoding="utf-8"?>
<Manager>
<SSH>
<Tunnels>
<Port>
<Local>443</Local>
<Remote>443</Remote>
</Port>
<Port>
<Local>5432</Local>
<Remote>5432</Remote>
</Port>
<Port>
<Local>19</Local>
<Remote>21</Remote>
</Port>
<Port>
<Local>19</Local>
<Remote>22</Remote>
</Port>
</Tunnels>
</SSH>
</Manager>
I was trying to select a 'Port' that had the values from a previous form so i can delete that specific entry from the xml. This was the code i was using:
//remove children from selected
XmlNode _xmlTunnel = _xml.SelectSingleNode("/Manager/SSH/Tunnels/Port[Local=" + _local + "] | /Manager/SSH/Tunnels/Port[Remote=" + _remote + "]");
MessageBox.Show("Local " + sshList.SelectedItems[0].Text + " Remote " + sshList.SelectedItems[0].SubItems[1].Text +"\n\n" + _xmlTunnel.InnerText);
_xmlTunnel.RemoveAll();
//remove all empties
XmlNodeList emptyElements = _xml.SelectNodes(#"//*[not(node())]");
for (int i = emptyElements.Count -1; i >= 0; i--) {
emptyElements[ i ].ParentNode.RemoveChild(emptyElements[ i ]); }
This code works fine until I have two Ports with the same Local Value. It always selects the first element that it comes to (i.e. Local=19 and Remote=21, even if you try to select the node where Local=19 and Remote=22). I tried switching the xpath expression to 'and' instead of '|' in the SelectSingleNode method but that errors out with a "Expression must evaluate to a node-set". Which makes me think that I am evaluating out to a boolean when I use 'and'.
Is the better way to do this by a loop where a select the first element and loop until the second one matches? As I said before i do not have much experience with xpath/xml expressions in C#, perhaps there is a better way. If it helps I am using windows forms and .net 4.0, in this form the port values fill a two column list view in detail view.
You need to "and" 2 conditions on Port node like:
"/Manager/SSH/Tunnels/Port[Local=" + _local + " and Remote=" + _remote + "]"
In your case you are doing union of 2 sets where Local=19 and another where Remote=21.

Categories

Resources