how to iterate the actual node and descendants? - c#

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.

Related

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>

Error on querying element using LINQ to XML

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

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.

Read sections from Config if name is unknown?

I have a simple program that would let the user add a section to a custom config file, it would have more settings than what is shown. I populate a datagridview with a list of all configurations. My problem is, the method to populate the listbox wouldn't know all the names of the sections that the user may have added, I'm trying to be dynamic. Is there an easy way to loop through these sections and get their names? Or do I have to make a Section, Collection, and Elements in order to to this?
Thanks.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="Jason" type="SQLQueryOutput.SQLQueryOutputConfigSection, SQLQueryOutput, Version=1.0.0.0, Culture=neutral, PublicKeyToken=760d257b40400289" />
<section name="Steve" type="SQLQueryOutput.SQLQueryOutputConfigSection, SQLQueryOutput, Version=1.0.0.0, Culture=neutral, PublicKeyToken=760d257b40400289" />
</configSections>
<Jason OutputFilePath="C:\temp\jason.txt" />
<Steve OutputFilePath="C:\temp\steve.txt" />
</configuration>
How about using Linq To Xml to parse your config file. For example,
var xDoc = XDocument.Load(configFile);
var sections = xDoc.XPathSelectElements("//configSections/section")
.Select(x=>x.Attributes().ToDictionary(a=>a.Name,a=>a.Value))
.ToList();
var name = sections[0]["name"];
or
var outputFilePaths = xDoc.Root.Elements()
.Where(d => d.Name.LocalName != "configSections")
.ToDictionary(e => e.Name.LocalName, e => e.Attribute("OutputFilePath").Value);
Actually your configSections element can contain sectionGroup elements also. With Linq to Xml:
XDocument xdoc = XDocument.Load(config_file_path);
var names = xdoc.Root.Element("configSections")
.Descendants("section") // selects also sectionGroup/section
.Select(s => (string)s.Attribute("name"));

How to add a line under a xml element using c#

<?xml version="1.0" encoding="utf-8"?>
<configSections>
----------
----------
</configSections>
<appSettings>
<add key="Name" Value="XXX">
<add key="Age" Value="10">
<!--<add key="Number" value="5"/>--><!--uncomment it-->
<!--<add key="Class" value="10"/>-->
</appSettings>
I want to uncomment the first commented line.
That's my code so far:
foreach (XmlElement xElement in xmlDoc.DocumentElement)
{
if (xElement.Name == "appSettings")
{
foreach (XmlNode xNodes in xElement.ChildNodes)
{
if (xNodes.NodeType == XmlNodeType.Comment)
{
if (xNodes.InnerText.Contains("Number"))
{
// Now the commented line is in xNodes.InnerText.
// How can i add this line in that xml file under appSettings?
}
}
}
break;
}
}
You can use this code:
var appSettingsNode = xmlDoc.DocumentElement
.ChildNodes
.Cast<XmlNode>()
.FirstOrDefault(x => x.Name == "appSettings");
if(appSettingsNode == null)
return;
var commentedNodes = appSettingsNode.ChildNodes
.Cast<XmlNode>()
.Where(x => x.NodeType == XmlNodeType.Comment
&& (x.InnerText.Contains("Number")
|| x.InnerText.Contains("Class")))
.ToList();
foreach(var commentedNode in commentedNodes)
{
var tmpDoc = new XmlDocument();
tmpDoc.LoadXml(commentedNode.InnerText);
appSettingsNode.ReplaceChild(xmlDoc.ImportNode(tmpDoc.DocumentElement, true),
commentedNode);
// Use this instead if you want to keep the commented line:
// appSettingsNode.AppendChild(xmlDoc.ImportNode(tmpDoc.DocumentElement, true));
}
The important part is the one inside the foreach loop.
Here we load the commented node into a new XmlDocument (tmpDoc) to get it as a "real" XmlNode back. Then we simply replace the comment node (commentedNode) with our newly loaded node (tmpDoc.DocumentElement).
The rest is your original code, just beautified. Please note how I reduced the cyclomatic complexity by replacing the loops and ifs with LINQ queries.
If you want to read Name key this is how you do:
string myUsername = System.Configuration.ConfigurationManager.AppSettings["Name"];
In C# .net you would usually use System.Configuration.ConfigurationManager.AppSettings to read an appSetting from the config file.
If you are trying to read the commented out line from the web.config then you are going to have to write some custom code to do this.
This answer should give you a good head start

Categories

Resources