Using LINQ to XML.
I have an XML file which looks like this:
<?xml version="1.0" encoding="utf-8"?>
<TileMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>title</Title>
<Abstract>Some clever text about this.</Abstract>
<SRS>OSGEO:41001</SRS>
<Profile>global-mercator or something</Profile>
</TileMap>
I can retrieve the <Title> from this with no problems by using this little piece of code:
string xmlString = AppDomain.CurrentDomain.BaseDirectory + #"Capabilities\" + name + ".xml";
string xmlText = File.ReadAllText(xmlString);
byte[] buffer = Encoding.UTF8.GetBytes(xmlText);
XElement element = XElement.Load(xmlString);
IEnumerable<XElement> title =
from el in element.Elements("Title")
select el;
foreach (XElement el in title)
{
var elementValue = el.Value;
}
However, this isn't very flexible because say I have an XML file that looks like this:
<?xml version="1.0" encoding="utf-8"?>
<RootObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Services>
<TileMapService>
<Title>title</Title>
<href>http://localhost/root</href>
</TileMapService>
</Services>
</RootObject>
It can't find <Title> but it finds <Services> (I presume) but since it's not called "Title" it just ignores it. I'm not very strong in working with XML. How would I go about making a method that looks through the XML and fetches me "Title" or however you'd implement this?
You're currently just looking at the child elements of the root element.
Instead, if you want to find all descendants, use Descendants.
Additionally, there's no point in using a query expression of from x in y select x (or rather, there's a very limited point in some cases, but not here). So just use:
var titles = element.Descendants("Title");
Personally I would actually use XDocument here rather than XElement - you have after all got a whole document, complete with XML declaration, not just an element.
Change your LINQ query to:
IEnumerable<XElement> title =
from el in element.Descendants("Title")
select el;
Elements returns only the immediate children, Descendants returns all descendant nodes instead.
Descendants will select all the "Title" elements irrespective of the level. Please use xpath to correctly locate the element
using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using System.IO;
public class Program
{
public static void Main()
{
string xmlFile = AppDomain.CurrentDomain.BaseDirectory + #"Capabilities\" + name + ".xml";
XElement xml=XElement.Load(xmlFile);
IEnumerable<XElement> titleElements = xml.XPathSelectElements("//Services/TileMapService/Title");
}
}
Related
<?xml version="1.0"?>
<TextType IsKey="false" Name="XMLReport"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Providers
xmlns="Reporting"/>
<Sales
xmlns="Reporting"/>
<Value
xmlns="Reporting">
<?xml version="1.0" encoding="utf-8"?>
<TestReport>
<StudyUid>
<![CDATA[123]]>
</StudyUid>
<Modality>
<![CDATA[XYZ]]>
</Modality>
<StudyDate format="DICOM">123456</StudyDate>
<StudyTime format="DICOM">6789</StudyTime>
<AccessionNumber>
<![CDATA[123]]>
</AccessionNumber>
<StudyDescription>
<![CDATA[abc def]]>
</StudyDescription>
<OperatorName format="xyz">
<![CDATA[abc]]>
</OperatorName>
<PhysicianReadingStudy format="xyz">
<![CDATA[^^^^]]>
</PhysicianReadingStudy>
<InstitutionName>
<![CDATA[xyz]]>
</InstitutionName>
<HospitalName>
<![CDATA[Hospital Name]]>
</HospitalName>
<ReportSet>
<MyReport ID="1">
<ReportStatus>
<![CDATA[Done]]>
</ReportStatus>
</MyReport>
<MyReport ID="2">
<ReportStatus>
<![CDATA[Done]]>
</ReportStatus>
</MyReport>
<MyReport ID="3">
<ReportStatus>
<![CDATA[Initial]]>
</ReportStatus>
</MyReport>
</ReportSet>
<ReportImageSet />
<FetusSet />
</TestReport>
</Value>
<WhoSetMe xmlns="Reporting">NotSpecified
</WhoSetMe>
</TextType>
I want to parse the xml above in C# and check whether "ReportStatus" is "Done" for all the ReportStatus under MyReport/ReportSet. One more twist here is the xml contains one more xml starts at "Value" tag as in above example.It may contatin many ReportStatus tag under ReportSet tag. Can someone please help me?
// Can you try this? I tried to do it with LINQ to XML.
// I assume you have multiple <TestReport /> elements in <Value /> tag
// and var xelement is your xml variable
// First we get all TestReport elemnts
IEnumerable<XElement> allReports =
from el in xelement.Elements("TextType/Value/TestReport")
select el;
// From allReports we get all MyReport elemnts
IEnumerable<XElement> allMyReports =
from el in allReports.Elements("ReportSet/MyReport")
select el;
// From allReports we also get all MyReport elemnts with element ReportStatus value equals "Done"
IEnumerable<XElement> allDoneMyReports =
from el in allMyReports
where (string)el.Element("ReportStatus") == "Done"
select el;
// Now we compare allMyReport with allDoneMyReports
if (allMyReports.Count() == allDoneMyReports.Count())
{
//DO Somehing
}
Your XML document is invalid. You need to fix it before trying to parse it. The issue is that a document can only have one top-level element; you have 2 <TextType> and <Providers>.
Most of your elements are the namespace Reporting. You need to use it when referencing the element.
XNamespace ns = "Reporting";
var value = doc.Element("Value" + ns);
Update
Just use the namespace for each element
XNamespace ns = "Reporting";
var value = xelement.Elements("Value" + ns);
Another Update
The XML document is considered invalid because it has multiple XML declarations; there is no way to disable this. I suggest you pre-process the document to remove the extra declarations. Here's an example (https://dotnetfiddle.net/UnuAF6)
var xml = "<?xml version='1.0'?><a> <?xml version='1.0'?><b id='b' /></a>";
var doc = XDocument.Parse(xml.Replace(" <?xml version='1.0'?", " "));
var bs = doc.Descendants("b");
Console.WriteLine("{0} 'b' elements", bs.Count());
I'm iterating through all the child elements of this XML file:
<?xml version="1.0" encoding="utf-8"?>
<users>
<user name="SemiViral" access="2" />
</users>
with this code:
XDocument doc = XDocument.Load("Users.xml");
Console.WriteLine(doc.Descendants("users").Count());
foreach (XElement u in doc.Descendants("users")) {
Console.WriteLine(u.Attributes().Count());
}
but the output from the WriteLine is 0, and similarly empty if I try referencing the attributes directly. Counting the descendants returns 1 and when I added inner contents to the single child element, it was able to output those. So I know that it's the correct element, it's simply not accessing the attributes for some reason.
Here is a code to do what you are trying to do. You were not getting results because you were only looking for users elements (doc.Descendants("users")). The element that you are looking for is at the next level of the xml. If you debugged your code you would have spotted it.
XDocument doc = XDocument.Load("Users.xml");
Console.WriteLine(doc.Descendants("users").Descendants().Count());
foreach (XElement u in doc.Descendants("users").Descendants())
{
Console.WriteLine("value of the attribute is " + u.Attributes("access").First().Value);
}
I have a problem, I can't get...and don't know what is wrong with it?
the code:
static void Main(string[] args)
{
XDocument doc = XDocument.Load(args[0] + "/?verb=GetRecord&metadataPrefix=p3dm&identifier=" + 1);
doc.Save("doc.xml");
var node = doc.Descendants("identifier");
foreach (var n in node)
{
doc.Save("file_" + n.Value + ".xml");
}
}
the doc.xml looks like:
<?xml version="1.0" encoding="utf-8"?>
<OAI-PMH xmlns="..." xmlns:xsi="..." xsi:schemaLocation="...">
<responseDate>...</responseDate>
<request verb="GetRecord" identifier="1"</request>
<GetRecord>
<record>
<header>
<identifier>1</identifier>
<datestamp>...</datestamp>
</header>
<metadata>
<P3DM xmlns="..." xsi:schemaLocation="...">
<MODELINFOID>1</MODELINFOID>
<TITLE>Roth</TITLE>
....
As we can see in doc.xml, there is element with the value 1, and using Descendants and foreach...I would like to save same document but using tag value to name my output file. What am I doing wrong. This code is just for testing.
Your XML has default namespace. You can use XNamespace plus element's local name to address an element in namespace :
......
//assume that the default namespace declared as <OAI-PMH xmlns="dummy.uri" ....>
XNamespace d = "dummy.uri";
var node = doc.Descendants(d+"identifier");
......
I have an XML e.g.
<?xml version="1.0" encoding="utf-8"?>
<A1>
<B2>
<C3 id="1">
<D7>
<E5 id="abc" />
</D7>
<D4 id="1">
<E5 id="abc" />
</D4>
<D4 id="2">
<E5 id="abc" />
</D4>
</C3>
</B2>
</A1>
This is may sample code:
var xDoc = XDocument.Load("Test.xml");
string xPath = "//B2/C3/D4";
//or string xPath = "//B2/C3/D4[#id='1']";
var eleList = xDoc.XPathSelectElements(xPath).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
It works perfectly, but if I add a namespace to the root node A1, this code doesn't work.
Upon searching for solutions, I found this one, but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for <E5> because the same tag exists for <D7>, <D4 id="1"> and <D4 id="2">
My requirement is to search if a node exists at a particular XPath. If there is a way of doing this using Descendants, I'd be delighted to use it. If not, please guide me on how to search using the name space.
My apologies in case this is a duplicate.
To keep using XPath, you can use something link this:
var xDoc = XDocument.Parse(#"<?xml version='1.0' encoding='utf-8'?>
<A1 xmlns='urn:sample'>
<B2>
<C3 id='1'>
<D7><E5 id='abc' /></D7>
<D4 id='1'><E5 id='abc' /></D4>
<D4 id='2'><E5 id='abc' /></D4>
</C3>
</B2>
</A1>");
// Notice this
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("sample", "urn:sample");
string xPath = "//sample:B2/sample:C3/sample:D4";
var eleList = xDoc.XPathSelectElements(xPath, nsmgr).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for because the same tag exists for , and
I'm pretty sure you're not quite understanding how that works. From the MSDN documentation:
Returns a filtered collection of the descendant elements for this document or element, in document order. Only elements that have a matching XName are included in the collection.
So in your case, just do this:
xDoc.RootNode
.Descendants("E5")
.Where(n => n.Parent.Name.LocalName == "B4");
Try this
var xDoc = XDocument.Parse("<A1><B2><C3 id=\"1\"><D7><E5 id=\"abc\" /></D7><D4 id=\"1\"><E5 id=\"abc\" /></D4><D4 id=\"2\"><E5 id=\"abc\" /></D4></C3></B2></A1>");
foreach (XElement item in xDoc.Element("A1").Elements("B2").Elements("C3").Elements("D4"))
{
Console.WriteLine(item.Element("E5").Value);//to get the value of E5
Console.WriteLine(item.Element("E5").Attribute("id").Value);//to get the value of attribute
}
Say I call XElement.Parse() with the following XML string:
var xml = XElement.Parse(#"
<?xml version="1.0" encoding="UTF-8"?>
<AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Owner>
<ID>7c75442509c41100b6a413b88b523bd6f46554cdbee5b6cbe27bc08cb3f6a865</ID>
<DisplayName>me</DisplayName>
</Owner>
<AccessControlList>
<Grant>
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Group">
...
");
When it comes time to query the element, I'm forced to use fully-qualified element names because that XML document contains an xmlns attribute in its root. This requires cumbersome creations of XName instances:
var AWS_XMLNS = "http://s3.amazonaws.com/doc/2006-03-01/";
var ownerElement = xml.Element(XName.Get("AccessControlPolicy", AWS_XMLNS)).Element(XName.Get("Owner", AWS_XMLNS));
When what I really want is simply,
var ownerElement = xml.Element("AccessControlPolicy").Element("Owner");
Is there a way to make LINQ to XML assume a specific namespace so I don't have to keep specifying it?
You could simplify by using
XNamespace ns = "http://s3.amazonaws.com/doc/2006-03-01/";
var ownerElement = xml.Element(ns + "AccessControlPolicy").Element(ns + "Owner");
I don't think you can (see Jon Skeet's comment), but there are a few tricks you can do.
1) create an extension method that appends the XNamespace to your string
2) Use VB?!?