Error on querying element using LINQ to XML - c#

This is a small section of a XML file I have.(see below).
I'm able to read what I need, like I can get the type and message etc. however in one of the PreflightResultEntry section there is a set of elements called Var I need the "<Var name="NumPages">8</Var>"
--
<PreflightResultEntry type="GeneralDocInfo">
<PreflightResultEntryMessage xml:lang="en-US">
<Message>457834a.pdf </Message>
<StringContext>
<BaseString>%FileInfo%</BaseString>
<Const name="Category">GeneralDocInfo</Const>
<Const name="ActionID">-1</Const>
<Instance>
<Var name="FileInfo">
<Var name="DIPath">/V/PitStop/Testing/Mike/Processed Docs on Success/457834a.pdf</Var>
<Var name="CreationDate">D:20120724153648-05'00'</Var>
<Var name="ModDate">D:20120725134534-04'00'</Var>
<Var name="Producer">Adobe PDF Library 10.0</Var>
<Var name="Creator">Acrobat PDFMaker 10.1 for Word</Var>
<Var name="Author">DOL Comments</Var>
<Var name="Title"/>
<Var name="Subject"/>
<Var name="Keywords"/>
<Var name="Trapped">1</Var>
<Var name="NumPages">8</Var>
<Var name="Major">1</Var>
<Var name="Minor">5</Var>
<Var name="WasRepairedOnOpen">0</Var>
<Var name="IsLinearized">0</Var>
<Var name="ContainsThumbnails">0</Var>
<Var name="LeftToRightReading">1</Var>
<Var name="ContainsJobTicket">0</Var>
<Var name="EncryptionType">1</Var>
<Var name="Permissions">-1</Var>
<Var name="PrinergyTraps">3</Var>
</Var>
<Location page="-1"/>
</Instance>
</StringContext>
</PreflightResultEntryMessage>
</PreflightResultEntry>
---
here what I have got that works for Messages and types
List<PitStopMessage> messages = XDocument.Load(file)
.Descendants("PreflightResultEntryMessage")
.Where(x => x.Parent != null )
.Select(x => new PitStopMessage()
{
message = x.Element("Message").Value,
type = x.Parent.Attribute("type").Value,
xmllevel = x.Parent.Attribute("level") != null ? x.Parent.Attribute("level").Value : String.Empty,
link = 0
}).ToList();
I need a new query to the elements var only if it exits in the parent element PreflightResultEntry
here what I have so far but its giving me a error "Object reference not set to an instance of an object." which is telling what I'm looking for (elements) don't exist.
List<PitStopPages> messages = XDocument.Load(file)
.Descendants("PreflightResultEntryMessage")
.Where(x => x.Parent != null && x.Parent.Attribute("type").Value == "GeneralDocInfo" && x.Parent.Element("Var").Value == "NumPages")
.Select(x => new PitStopPages()
{
Pages = x.Parent.Attribute("name").Value
}).ToList();

If you logically read through your query, you might spot the mistake.
Your query is trying to find:
An element called PreflightResultEntryMessage
Where its parent has a type attribute of GeneralDocInfo
Where its parent element's first child named Var has the value NumPages
Get the parent element's name attribute value
The issue is the last two parts. Parent in all your cases is the PreflightResultEntry element. PreflightResultEntry does not have any child Var element, no Var elements have the value NumPages, and PreflightResultEntry has no name attribute. Any one of these would cause a null reference exception (the exception you're seeing).
It's probably best to approach this from the top down rather than finding an element and then looking back up to its parent. So:
Find an element called PreflightResultEntry with a type attribute of GeneralDocInfo
Get a descendant Var element has a name attribute of NumPages
So:
var numPages = (int)XDocument.Load(file)
.Descendants("PreflightResultEntry")
.Where(x => (string) x.Attribute("type") == "GeneralDocInfo")
.Descendants("Var")
.Single(x => (string) x.Attribute("name") == "NumPages");
See this fiddle for a working demo.

I suggest using XPath to select the node you want. You can use the method XPathSelectElements and pass in your XPath. If you need to select only one node, you can use XPathSelectElement (without 's') instead.
In your situation, I think the XPath should be: /PreflightResultEntry//Var[#name='NumPages']
You can test the XPath here.

Let me expand on what #AnhTriet suggested,
var xml = new XmlDocument();
xml.LoadXml(str); // suppose that str string contains your xml
var xnList = xml.SelectNodes("/PreflightResultEntry//Var[#name='NumPages']");
foreach (XmlNode xn in xnList)
{
Console.WriteLine(xn.InnerText); //this should print the 8
}
.NET Fiddle
To be able to use the XPathSelectElement method you need to load first the xml using the XDocument class
So, it would be something like this:
var xml = XDocument.Load(file);
XmlNode node = xml.XPathSelectElement("/PreflightResultEntry//Var[#name='NumPages']");
Console.WriteLine(node.InnerText);

Related

how to iterate the actual node and descendants?

I have this xml file:
<Assemblies>
<Assembly Name="MyMainAssambly.exe" Version="1.7.0.0" Path="x:\MyMainAssemby.exe" >
<NeededAssemblies>
<Assembly Name="Aseembly01.dll" Version="1.1.1.1" Path="x:\Assembly01.dll"/>
<Assembly Name="Aseembly02.dll" Version="2.2.2.2" Path="x:\Assembly02.dll"/>
<Assembly Name="Aseembly03.dll" Version="3.3.3.3" Path="x:\Assembly03.dll"/>
</NeededAssemblies>
</Assembly>
</Assemblies>
I want to iterate all of them to do some tasks. So I am trying to use this code:
XElement myMainAssembly = miDocumentoXml.Root.Elements("Assembly")
.Where(x => x.Attribute("Name").Value == "MyMainAssembly.exe"
&& x.Attribute("Version").Value.StartsWith("1.7.0.0"))
.FirstOrDefault();
foreach (XElement nodeIterator in myMainAssembly.DescendantNodesAndSelf())
{
//Do some code with the nodes Assembly01.dll, Assembly02.dll and Assembly, using the name, version and path.
string miStrAssemblyName = nodeIterator .Attribute("Name").Value;
}
But if I debbug and set a break point, I can see that the property HasAttributes of all elements is false. Why? Because in the xml all the elements have 3 attriibutes. In fact, in the first part of code, where I get my main assembly, I can use Attribute("Name") to get the name of the main assembly. But later in the foreach I can't.
So I would like to know how to iterate the elements and get the information of the attributes.
Thanks.

How do I find a specific XML attribute in a specific place in an XDocument

I have an example of my XML below (which i'm converting from SGML to XML prior to the check but that's besides the point). The XML IS valid at the point of checking.
I need check the XDocument and find out if any of the <version> elements contained within the <status> section at the top of the file contain the attriubute "RNWB". I need to be explicit about the fact that i'm only interested in checking <version> elements that are children of <status> elements because there might be other <version> elements within the document which i don't care about.
<dmodule>
<idstatus>
<dmaddres>
<dmc>Blah</dmc>
<dmtitle><techname>System</techname><infoname>Introduction</infoname></dmtitle>
<issno issno="006" type="revised"/>
<issdate year="2016" month="11" day="30"/>
</dmaddres>
<status>
<security class="2"/>
<rpc></rpc>
<orig></orig>
<applic>
<model model="2093">
<version version="BASE"></version>
<version version="RNWB"></version></model>
</applic>
<techstd>
<autandtp>
<authblk></authblk>
<tpbase></tpbase>
</autandtp>
<authex></authex>
<notes></notes>
</techstd>
<qa>
<firstver type="tabtop"/></qa>
<remarks></remarks>
</status>
</idstatus>
<content>
<refs><norefs></refs>
<descript>
<para0><title>Introduction</title>
<para>The system many new features included which are safe and accurate blah blah blah.</para>
</descript>
</content>
</dmodule>
I've tried all sorts but can't seem to get a result. Here's one example of what i've tried:
var result = (from ele in doc.Descendants("applic").Descendants("version").Attributes("RNWB")
select ele).ToList();
foreach (var v in result)
{
File.Move(file, Path.Combine(outputFolder, fileName)); // move the file to the new folder
}
If you have to be explicit that the version element is within a status element, then you need to be explicit about that in your query too. Your example doesn't include version anywhere.
You then need to find a version attribute with the value RNWB. You're currently looking for an RNWB attribute, which doesn't exist.
var hasFlag = doc
.Descendants("status")
.Descendants("version")
.Attributes("version")
.Any(x => x.Value == "RNWB");
See this fiddle for a demo.
Something like this should work:
// Load document
XDocument _doc = XDocument.Load("C://t//My File2.txt");
Select path to model
Select the elements version
Where the attribute == RNWB
Put them in a list for the result
// if you need to get the version element where the atrribute == something
List<XElement> model = _doc
.XPathSelectElement("dmodule/idstatus/status/applic/model")
.Elements("version")
.Where(x => x.Attribute("version").Value == "RNWB")
.ToList();
// if you only need to check if the attribute exists
bool contains = _doc
.XPathSelectElement("dmodule/idstatus/status/applic/model")
.Elements("version")
.Any(x => x.Attribute("version").Value == "RNWB");
The XML is not in valid format: I made some adjustment and got it working
<dmodule>
<idstatus>
<dmaddres>
<dmc>Blah</dmc>
<dmtitle><techname>System</techname><infoname>Introduction</infoname></dmtitle>
<issno issno="006" type="revised"/>
<issdate year="2016" month="11" day="30"/>
</dmaddres>
<status>
<security class="2"/>
<rpc></rpc>
<orig></orig>
<applic>
<model model="2093">
<version version="BASE"></version>
<version version="RNWB"></version>
</model>
</applic>
<techstd>
<autandtp>
<authblk></authblk>
<tpbase></tpbase>
</autandtp>
<authex></authex>
<notes></notes>
</techstd>
<qa>
<firstver type="tabtop"/></qa>
<remarks></remarks>
</status>
</idstatus>
<content>
<refs>
<norefs>
</norefs>
</refs>
<descript>
<para0>
<title>Introduction</title>
<para>The system many new features included which are safe and accurate blah blah blah.</para>
</para0>
</descript>
</content>
</dmodule>

Get Top Most Parent From XML

How do I get the top most parent element from XML? I need the entire element with its attributes.
It wont always be the first line as there might be comments.
string xmlStr = File.ReadAllText(#"C:\Users\GRPAdmin\Desktop\Test.xml");
XElement str = XElement.Parse(xmlStr);
var h1 = str.Parent;
var h2 = str.XPathSelectElements("..").FirstOrDefault();
var h3 = str.XPathSelectElement("..").Parent;
<FILE NAME="ABC" version="14.0.0.112" State="WIP" Template="ABC123" origin="designer">
<REC NAME="Recipient">
<FLD NAME="FirstName">James</FLD>
</REC>
<REC NAME="Message">
<FLD NAME="Key">123</FLD>
</REC>
<REC NAME="Details">
<FLD NAME="Key">default</FLD>
</REC>
</File>
I would expect to have a var that equals <FILE NAME="ABC" version="14.0.0.112" State="WIP" Template="ABC123" origin="designer"> as the desired result
In XML data model, the opening tag (including all the attributes), tag content, and the closing tag is single XML element object, so it isn't natural to request only the opening tag. I'm not aware of a bult-in .NET function to get that, but you can reconstruct the opening tag string by combining information of the tag name, and all the attribute name-value pairs, for example :
string xmlStr = File.ReadAllText(#"C:\Users\GRPAdmin\Desktop\Test.xml");
XElement str = XElement.Parse(xmlStr);
var attributes = String.Join(" ", str.Attributes().Select(o => String.Format("{0}=\"{1}\"", o.Name, o.Value)));
var result = string.Format("<{0} {1}>", str.Name, attributes);
Console.WriteLine(result);
Dotnetfiddle Demo
output :
<FILE NAME="ABC" version="14.0.0.112" State="WIP" Template="ABC123" origin="designer">
You can use XElement.AncestorsAndSelf() to walk up the chain of parent XML elements to the root element. Enumerable.Last then gives you the root element:
var root = element.AncestorsAndSelf().Last();
If the XElement is contained by some XDocument, you can always do
var root = element.Document.Root;
But in your case you parsed directly to an XElement without bothering to create an XDocument container.
as #har07 already said, str is the top most element. If you want the attributes just iterate over them
string xmlStr = File.ReadAllText(#"C:\Users\GRPAdmin\Desktop\Test.xml");
var root = XElement.Parse(xmlStr);
foreach (var attribute in root.Attributes())
{
Console.WriteLine("{0} : {1}", attribute .Name, attribute.Value);
}

Unable to remove root node from an xml document using linq to xml c#

Actullay, I need to get all elements except root node from first xml document and so that I could insert them as child nodes to an element(that has same name as a previous doc's root name) in a new document.
So I have tried various ways to achieve it, one of them is removing the root node of first and then trying to add elements to a new one's as given below:
I have tried the following but could not achieve it.
XDocument testDoc = XDocument.Parse(Mydocument);
testDoc.Descendants().Where(e => e.Name.LocalName == "rootName").Select(m=>m).Single().Remove();
var resultDoc = testDoc;
The above code is giving me an empty "{}" result.
my xml document looks something like the below one's:
<rootName xsi:schemaLocation="" xmlns:xsi="" xmlns="">
<main>
<child>
</child>
<anotherchild>
</anotherchild>
</main>
</rootName>
And another way is getting all the elements of first document as the following:
var resultDoc = testDoc.Descendants(ns + "rootName").Elements();
the above statement is giving me the list of elements in the "testDoc" which
I need to do something like below, I am clueless:
<AnotherDocument xsi:schemaLocation="" xmlns:xsi="" xmlns="">
<firstNode>
<rootName>
<main>
<child>
</child>
<anotherchild>
</anotherchild>
</main>
</rootName>
</firstNode>
Please let me know how to insert those elements in a new document as above if I am correct else let me know the way to resolve this issue.
Thanks in advance.
You can replace content of rootName element in another document with elements from first document root:
var xDoc = XDocument.Parse(Mydocument);
var anotherXDoc = XDocument.Load("anotherdata.xml");
XNamespace ns = "http://..."; // your xml namespance
var rootName = anotherXDoc.Descendants(ns + "rootName").First();
rootName.ReplaceNodes(xDoc.Root.Elements());
By this page_nodes gets all nodes now you can used all node by for each loop
var page_nodes = from p in xdoc.Descendants.Where(e => e.Name.LocalName == "rootName").Select(m=>m).Single().Remove() select p;
foreach (var page_node in page_nodes)
{
//Do stuff
}
Wouldn't removing a root node, remove all its child nodes as well? The result you are getting is to be expected I think. You should probably get all the children of the root and copy them to your new document.

Locating a value in XML

I have an xml file loaded into an XDocument that I need to extract a value from, and I'm not sure of the best way to do it. Most of the things I'm coming up with seem to be overkill or don't make good use of xml rules. I have the following snippet of xml:
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
There can be any number of <entry> nodes, and they will all follow a similar pattern. The value under the root attribute on the templateId element contains a known UID that identifies this entry as the one I want. I need to get the reference value.
My thought is to find the correct templateID node, back out to the observation node, find <valuexsi:type="ED"> and then get the reference value. This seems overly complex, and I am wondering if there is another way to do this?
EDIT
The xml I receive can sometimes have xml nested under the same node name. In other words, <observation> may be located under another node named <observation>.
You have problems, because your document uses Namespaces, and your query is missing them.
First of all, you have to find xsi namespace declaration somewhere in your XML (probably in the most top element).
It will look like that:
xmlns:xsi="http://test.namespace"
The, take the namespace Uri and create XNamespace instance according to it's value:
var xsi = XNamespace.Get("http://test.namespace");
And use that xsi variable within your query:
var query = from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
let tId = o.Element("templateId")
where tId != null && (string)tId.Attribute("root") == "2.16.840.1.113883.10.20.6.2.12"
let v = o.Element("value")
where v != null && (string)v.Attribute(xsi + "type") != null
let r = v.Element("reference")
where r != null
select (string)r.Attribute("value");
var result = query.FirstOrDefault();
I have tested it for following XML structure:
<root xmlns:xsi="http://test.namespace">
<entries>
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
</entries>
</root>
The query returns #121071 for it.
For your input XML you will probably have to change first line of query:
from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
to match <observation> elements from your XML structure.
Would something along the lines of the following help?
XDocument xdoc = GetYourDocumentHere();
var obsvlookfor =
xdoc.Root.Descendants("observation")
.SingleOrDefault(el =>
el.Element("templateId")
.Attribute("root").Value == "root value to look for");
if (obsvlookfor != null)
{
var reference = obsvlookfor
.Element("value")
.Element("reference").Attribute("value").Value;
}
My thought is as follows:
Pull out all the observation elements in the document
Find the only one (or null) where the observation's templateId element has a root attribute you're looking for
If you find that observation element, pull out the value attribute against the reference element which is under the value element.
You might have to include the Namespace in your LINQ. To retrieve that you would do something like this:
XNamespace ns = xdoc.Root.GetDefaultNamespace();
Then in your linq:
var obsvlookfor = xdoc.Root.Descendants(ns + "observation")
I know I had some issues retrieving data once without this. Not saying its the issue just something to keep in mind particularly if your XML file is very in depth.

Categories

Resources