Getting the right node in Linq to XML - c#

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.

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

Selecting attribute in xml

I am trying to select "Example language" in the xml code below.
This is the C#:
XNamespace gml = "http://www.example.net/gdl";
XElement Xmlwater = XElement.Parse(e.Result);
listBox3.ItemsSource = from Zwemwater in Xmlwater.Descendants(zwr + "Location")
select new water
{
water_name = water.Element(zwr + "Name").Value,
water_language = water.Element(zwr + "language").Value, // How to select the "nl" ?
};
This is the XML:
<zwr:Location>
<zwr:Name>test<zwr:Name>
<zwr:Example language="nl"> Example text </zwr:beschrijving>
<zwr:Example language="en"/>
</zwr:Location>
What is the best way to get the Example language="nl so that i can bind it.
Thank you in advance.
Assuming water is <zwr:Location> element, you can do as follow :
water_language = water.Element(zwr + "Example").Attribute(zwr + "language").Value
That will select child element of Location that has name Example, then get value of that child element's language attribute. PS: I'm not sure if attribute name considering namespace or not, but in the sample above I assume it is
Try the .Attributes property instead of the .Element property.
water_language = water.Attributes(zwr + "language").Value

.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

how to find an element value in xml when we have multiple namespaces

I want to get the value of the element rsm:CIIHExchangedDocument/ram:ID
But I have problem with multiple namespaces, and null values (I can not know if requested element exists)
It can be achieved this way:
XElement invoice = XElement.Load(invoiceStream);
XNamespace rsm = invoice.GetNamespaceOfPrefix("rsm");
XNamespace ram = invoice.GetNamespaceOfPrefix("ram");
if ((invoice.Element(rsm + "CIIHExchangedDocument")) != null)
{
if (invoice.Element(rsm + "CIIHExchangedDocument").Element(ram + "ID") != null)
{
string id = invoice.Element(rsm + "CIIHExchangedDocument").Element(ram + "ID").Value;
}
}
but I think using xPath will suit my needs better. I want to do something like this:
invoice.XPathSelectElement("rsm:CIIHExchangedDocument/ram:ID"):
I need to retrieve a lot of elements of different depth in the document, and I have many namespaces.
What is the simplest whay to achive this? Execution speed is also important to me.
I believe what you are looking for is the XPathNavigator class. An example of how to use can be found here XpathNaivigator

Extract descendant LINQ to XML

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

Categories

Resources