Retrieve processing instructions using XDocument - c#

I have an XML document containing processing instructions. I know that, with the XmlDocument class, you can use
var node = xmlDoc.SelectSingleNode("processing-instruction('xml-stylesheet')") as XmlProcessingInstruction;
but I want to use XDocument. How can I do this?

This is how I access an XML file's nodes with the XDocument class.
However, you'll have to be more specific on what you want to do with it.
XDocument doc = XDocument.Load("filepath");
var node = doc.Nodes().OfType<XElement>().SingleOrDefault(n => n.Name == "node name");
var node_value = node.Value;
var node_descendants = node.Descendants();
UPDATE:
As you may have noticed there's no SelectSingleNode in XDocument, in fact, to retrieve the node you want you'll have to fetch it from the corresponding ienumerable collection, or alternatively from the predefined FirstNode, NextNode, PreviousNode, LastNode, but you cannot apply any filters to those. Therefore the only ways to retrieve ProcessingInstruction nodes are
var pI_nodes = doc.Nodes().OfType<XProcessingInstruction>();
And
var pI_nodes = (from node in doc.Nodes()
where node.NodeType == System.Xml.XmlNodeType.ProcessingInstruction
select node);
If you expect to retrieve several ProcessingInstructions and need to filter these as well, the equivalent to the node name would the Target property
var filtered_pIs = pI_nodes_1.Where(pI => pI.Target == "xml-stylesheet");
And as a final reminder the value of the processing instruction is stored in the Data property.
string pI_value = filtered_pIs.First().Data

Here is one way:
var node = xDoc.Root.Nodes().OfType<XProcessingInstruction>().First();

Related

XElement.Root.Element keeps returning null

I am learning C# and trying to flatten XML following this code from this post
var doc = XDocument.Load("test.xml");
XNamespace ns = "mynamespace";
var member = doc.Root.Element(ns + "member");
// This will *sort* of flatten, but create copies...
var descendants = member.Descendants().ToList();
// So we need to strip child elements from everywhere...
// (but only elements, not text nodes). The ToList() call
// materializes the query, so we're not removing while we're iterating.
foreach (var nested in descendants.Elements().ToList())
{
nested.Remove();
}
member.ReplaceNodes(descendants);
This is my XML (sorry I don't know how to post fancy code style)
<ApplicationExtraction>
<IsCurrent>Yes</IsCurrent>
<ApplicationDate>10/06/2015</ApplicationDate>
<Status>Application Received</Status>
<EquipmentType>Equipment</EquipmentType>
<IsLoan>No</IsLoan>
</ApplicationExtraction>
There is namespace so I changed var member = doc.Root.Element(ns + "member"); to var member = doc.Root.Element("ApplicationExtraction"); but this returns NULL.
I also try XElement sRoot = doc.Root.Element("ApplicationExtraction"); from this post I still get the same result.
I read up Microsoft XElement document but don't see how I can fix this.
What could I have done wrong?
XElement sRoot = doc.Root.Element("ApplicationExtraction");
will look for an element 'ApplicationExtraction' inside the Root.
If you want the Root, just reference
doc.Root
In your XML, doc.Root is the root node i.e. ApplicationExtraction and there is no node ApplicationExtraction inside the root node thus you are getting null.
To fetch any specific node you need(for example):-
XElement member = doc.Root.Element("IsCurrent");
and to fetch the value inside the node:-
string member = (string)doc.Root.Element("IsCurrent");

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

Query Collection of all XML Elements

I'm looking to parse an XML file, and have the ability to search any element.
My XML code looks rather nontraditional (out of my control):
<DiagReport>
<LicensingData>
<ToolVersion>6.3.9431.0</ToolVersion>
<LicensingStatus>SL_LICENSING</LicensingStatus>
</LicensingData>
<Health>
<Result>PASS</Result>
<TamperedItems></TamperedItems>
</Health>
<Genuine>
<ServerProps>GenuineId</ServerProps>
</Genuine>
</DiagReport>
I'd like to load each singular element into a collection, i.e. one collection including ToolVersion, Result, etc. From there, I'd like to iterate through each element/name pair, and depending upon the element, do something different:
if (element.Equals(Result))
//do something with "PASS"
Is this possible to do with LINQ?
You can use LINQ to XML to iterate through all second level elements:
var xdoc = XDocument.Load(#"C:\test.xml");
foreach (var element in xdoc.Element("DiagReport").Elements().Elements())
{
if (element.Name == "Result")
{
string value = element.Value;
}
}
You can use Linq to Xml to query your Xml document, you can search Elements, Attributes and grab what you need.
More information can be found here: Basic Queries (LINQ to XML)
And you can creat simple querys like below.
XElement root = XElement.Parse(File.ReadAllText("C:\\Test.xml"));
var test = new
{
ToolVersion = root.Descendants("ToolVersion").FirstOrDefault().Value,
Result = root.Descendants("Result").FirstOrDefault().Value
};

xPath to get sibling value for element with specific value

Given this XML:
<InitResponse>
<LottoToken>908ec70b308adf10d04db1478ef9b01b</LottoToken>
<GameInfoList>
<GameInfo>
<Draw>
<gameId>L649</gameId>
<draw>3035</draw>
</Draw>
</GameInfo>
<GameInfo>
<Draw>
<gameId>BC49</gameId>
<draw>2199</draw>
</Draw>
</GameInfo>
</GameInfoList>
</InitResponse>
I need to get the draw number based on a specific gameId. For example if I specify gameID L649 I need to get 3035.
The following works in several online evaluators, but not in C#. It says it cannot find it. Suggestions?
/InitResponse/GameInfoList/GameInfo/Draw/draw[preceding-sibling::gameId='L649']
C# Code I've tried:
XmlNode node = xmlDoc.SelectSingleNode("/InitResponse/GameInfoList/GameInfo/Draw/draw[preceding-sibling::gameId='L649']");
... where xmlDoc is an xmlDocument object loaded with the xml. the node variable ends up with a null value which seems to indicate there was no match found.
Here is xpath (with Linq)
var xdoc = XDocument.Load(path_to_xml);
string xpath = "/InitResponse/GameInfoList/GameInfo/Draw[gameId='L649']/draw";
var draw = xdoc.XPathSelectElement(xpath);
if (draw != null) // check if draw with gameId found in xml
value = (int)draw;
Also you can use pure Linq to Xml (but in this case xpath looks more compact):
var draw = xdoc.Descendants("GameInfo")
.SelectMany(g => g.Elements("Draw"))
.SingleOrDefault(d => (string)d.Element("gameId") == "L649");
if (draw != null)
value = (int)draw.Element("draw");
Using XmlDocument
I didn't saw something wrong in your XPath statement, look on the following:
(So i guess there is something else that is wrong)
XmlDocument myDoc = new XmlDocument();
String str = #"<InitResponse>
<LottoToken>908ec70b308adf10d04db1478ef9b01b</LottoToken>
<GameInfoList>
<GameInfo>
<Draw>
<gameId>L649</gameId>
<draw>3035</draw>
/Draw>
</GameInfo>
<GameInfo>
<Draw>
<gameId>BC49</gameId>
<draw>2199</draw>
</Draw>
</GameInfo>
</GameInfoList>
</InitResponse>";
myDoc.LoadXml(str);
XmlNode node =
myDoc.SelectSingleNode("/InitResponse/GameInfoList/GameInfo/Draw/draw[preceding-sibling::gameId='L649']");
The node which returns from the result is: 3035
Note: your first note have to be <InitResponse> otherwise it will returns null

Finding element in XDocument?

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

Categories