I'm having trouble using XElement to parse multiple elements through an XUnit XML file and return the value.
Here is the XML File
<assemblies timestamp="07/31/2018 14:58:48">
<assembly name="C:\Users\bf\Documents\Visual Studio 2015\Projects\xUnitDemo\xUnitDemo\bin\Debug\xUnitDemo.DLL" environment="64-bit .NET 4.0.30319.42000 [collection-per-class, parallel (1 threads)]" test-framework="xUnit.net 2.3.1.3858" run-date="2018-07-31" run-time="14:58:47" config-file="C:\Users\bf\Documents\Visual Studio 2015\Projects\xUnitDemo\packages\xunit.runner.console.2.4.0\tools\net452\xunit.console.exe.Config" total="15" passed="14" failed="1" skipped="0" time="0.257" errors="0">
<errors />
<collection total="2" passed="1" failed="1" skipped="0" name="Test collection for xUnitDemo.SimpleTests" time="0.070">
<test name="xUnitDemo.SimpleTests.PassingTest" type="xUnitDemo.SimpleTests" method="PassingTest" time="0.0636741" result="Pass">
<traits>
<trait name="test" value="test" />
<trait name="requirement" value="test" />
<trait name="labels" value="test" />
</traits>
</test>
<test name="xUnitDemo.SimpleTests.FailingTest" type="xUnitDemo.SimpleTests" method="FailingTest" time="0.0059474" result="Fail">
<failure exception-type="Xunit.Sdk.EqualException">
<message><![CDATA[Assert.Equal() Failure\r\nExpected: 5\r\nActual: 4]]></message>
<stack-trace><![CDATA[ at xUnitDemo.SimpleTests.FailingTest() in C:\Users\smsf\documents\visual studio 2015\Projects\xUnitDemo\xUnitDemo\SimpleTests.cs:line 30]]></stack-trace>
</failure>
</test>
</collection>
</assembly>
</assemblies>
I'm able to parse through test element using this code.
private static List<TestResults> GetTestAutomationExecutionResult(string filePath)
{
List<TestResults> testResults = new List<TestResults>();
XElement xelement = XElement.Load(filePath);
IEnumerable<XElement> results = xelement.Elements().Where(e => e.Name.LocalName == "test");
foreach (var result in results)
{
if (result.Attribute("result").Value == "Fail")
{
testResults.Add(new TestResults(result.Attribute("result").Value, "this is where the failure message would go"));
}
else
{
testResults.Add(new TestResults(result.Attribute("result").Value, ""));
}
}
But I'm having a hard time trying to find and add message inside of failure element in the foreach.
result.Attribute("message").Value
Your code has a couple problems:
The <result> elements are not direct children of the root element, so xelement.Elements().Where(e => e.Name.LocalName == "test") does not select anything. You need to descend deeper into the hierarchy, e.g. with Descendants().
The message text is contained in an indirect child element of the <test> node, specifically failure/message. You need to select this element to get the message.
result.Attribute("message").Value will not work because the XElement.Attribute(XName) method selects an XML attribute rather than an element.
See: XML attribute vs XML element.
Putting those two points together, your code should look like:
private static List<TestResults> GetTestAutomationExecutionResult(string filePath)
=> GetTestAutomationExecutionResult(XElement.Load(filePath));
private static List<TestResults> GetTestAutomationExecutionResult(XElement xelement)
{
var query = from e in xelement.Descendants()
where e.Name.LocalName == "test"
let r = e.Attribute("result").Value
let m = r == "Fail" ? e.Elements("failure").Elements("message").FirstOrDefault()?.Value : ""
select new TestResults(r, m);
return query.ToList();
}
Demo fiddle here.
Related
This seems like a simple question but I can't seem to get started on a working solution. The final goal is to change the value of the ConstantValue element highlighted below. My strategy is to find the Component node and drill down from there. The problem is that keep returning a null and I'm not sure why. Below is the code I'm using a the xml I'm using. Any hints would be great.
XDocument xmlDoc = XDocument.Parse(str);
var items = xmlDoc.Descendants("Component")
.Where(x => x.Attribute("Name").Value == "axesInterface")
.FirstOrDefault();
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Engineering version="V17" />
<DocumentInfo>
</DocumentInfo>
<SW.Blocks.FB ID="0">
<AttributeList>
<Interface><Sections></Sections></Interface>
<MemoryLayout>Optimized</MemoryLayout>
<MemoryReserve>100</MemoryReserve>
<Name>EM00_CM01_Warp1</Name>
<Number>31650</Number>
<ProgrammingLanguage>LAD</ProgrammingLanguage>
<SetENOAutomatically>false</SetENOAutomatically>
</AttributeList>
<ObjectList>
<SW.Blocks.CompileUnit ID="4" CompositionName="CompileUnits">
<AttributeList>
<NetworkSource>
<FlgNet xmlns="http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4">
<Parts>
<Access Scope="GlobalVariable" UId="27">
<Symbol>
<Component Name="HMIAxisCtrl_Interface" />
<Component Name="axesInterface" AccessModifier="Array">
<Access Scope="LiteralConstant">
<Constant>
<ConstantType>DInt</ConstantType>
<ConstantValue>0</ConstantValue>
</Constant>
</Access>
</Component>
</Symbol>
</Access>
</Parts>
</FlgNet>
</NetworkSource>
</AttributeList>
</SW.Blocks.CompileUnit>
</ObjectList>
</SW.Blocks.FB>
</Document>
You can use XQuery to get the correct node:
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
//
var nm = new XmlNamespaceManager(new NameTable());
nm.AddNamespace("sm", "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4");
var node = xdoc.XPathSelectElement(#"//sm:Access[#Scope=""LiteralConstant""]/sm:Constant/sm:ConstantValue", nm);
node.Value = "Something Else";
dotnetfiddle
For multiple nodes, change XPathSelectElement to XPathSelectElements
On your XML example, you can simply get specified node with this piece of code at any depth:
XDocument xmlDoc = XDocument.Parse(str);
XElement node = xmlDoc.Descendants().FirstOrDefault(x => x.Name.LocalName == "ConstantValue");
node.Value = "New value";
xmlDoc.Save(somewhere);
If there may be multiple nodes with "ConstantValue" name - then just replace FirstOrDefault with Where and work with filtered by names IEnumerable<XElement>.
Why it isn't find by x.Name == "ConstantValue":
EDIT: added samples.
// That's your parent node
var componentNode = xmlDoc.Descendants()
.Where(x => x.Name.LocalName == "Component"
&& (string)x.Attribute("Name") == "axesInterface");
// Get first ConstantValue node from parent Component node
var constantValueNode = componentNode.Descendants()
.FirstOrDefault(x => x.Name.LocalName == "ConstantValue");
// or get all ConstantValue nodes from parent Component node
var constantValueNodes = componentNode.Descendants()
.Where(x => x.Name.LocalName == "ConstantValue");
if (constantValueNode is XElement)
constantValueNode.Value = "New value";
You can create extension method to get parent node by specifying level:
public static class Extensions
{
public static XElement GetParentNode(this XElement element, int parentLevel)
{
XElement node = element.Parent;
for (int i = 0; i < parentLevel; i++)
node = node.Parent;
return node;
}
}
In your example, var parent = constantValueNode.GetParentNode(2); will return Component node (with attribute Name="axesInterface"). var parent = constantValueNode.GetParentNode(12); will return root "Document" node.
I'm trying to use LINQ to parse data from an XML file, but the code I have does not seem to work and I cannot figure out what I'm doing wrong.
Shortened version of the xml:
<Components>
....
<Component Id="b3d06054-6113-4775-9353-f48aa21295e8" ProductId="ERDDMR">
<Sections>
<ComponentSection Id="bb05507e-200d-494a-9aef-3181c039efc7" ProductSectionId="ERDDMR.Process" />
<ComponentSection Id="391aead4-cfeb-4739-b8ec-c6b12664189f" ProductSectionId="ERDDMR.Exhaust" />
</Sections>
<VariantData Type="eContact">
<Row Name="dampersize" Value="5610" Description="Return Damper Size" />
<Row Name="damperactuators" Value="1" Description="Return (0=None, 1=2-Pos, 2=MOD)" />
<Row Name="damperconstruction" Value="1" Description="Return (0=N/A, 1=VCD-23, 2=VCD-34" />
</VariantData>
</Component>
<Component Id="f4130a92-aac1-4039-a4df-83d6994ae095" ProductId="ERDSIC">
<Sections>
<ComponentSection Id="1e65f0c4-db4f-4eb7-8605-e37f9d7e6f68" ProductSectionId="ERDSIC.1" />
</Sections>
<VariantData Type="eContact">
<!-- *** Find this one, below! *** -->
<Row Name="dampersize" Value="5926" Description="MUA Damper Size" />
<Row Name="damperactuators" Value="1" Description="MUA (0=None, 1=2-Pos, 2=MOD)" />
<Row Name="damperconstruction" Value="1" Description="MUA (0=N/A, 1=VCD-23, 2=VCD-34, 3=VCD-40" />
</VariantData>
</Component>
...
</Components>
I'm trying to find the attribute Value of the Row Element that has attribute Name = "dampersize" and is a descendant of the Element "Component" whose attribute ProductId = "ERDSIC" (I identified it in the xml above)
My failed attempt is here:
var prop = xDoc.Elements("Component")
.Where(c => c.Attribute("ProductId").Value == "ERDSIC")
.Descendants("Row").Where(t => t.Attribute("Name").Value == "dampersize")
.Select(v => v.Attribute("Value").Value).FirstOrDefault();
Console.WriteLine("Result: " + prop.ToString());
The error I get is (located on the Console.WriteLine line):
System.NullReferenceException: Object reference not set to an instance of an object
EDIT - Changed "Type" to "Name" the code is still wrong
If xDoc is XDocument type:
var xDoc = XDocument.Load("test.xml");
then use the Root property:
var prop = xDoc.Root.Elements("Component")
Or change the xDoc type to XElement:
var xDoc = XElement.Load("test.xml");
Then your code will be fully working.
I suggest you cast to get value instead of use the Value property to avoid this kind of error, I think there is a node that doesn't have the attribute or you misspelled some name,
var prop = xDoc.Root.Elements("Component")
.Where(c => (string)c.Attribute("ProductId") == "ERDSIC")
.Descendants("Row").Where(t => (string)t.Attribute("Name") == "dampersize")
.Select(v => (string)v.Attribute("Value")).FirstOrDefault();
If I put the OP's XML in a file called Data.xml in the \bin\Debug folder and run the following, it works, so I can only assume that his method for loading the XML is not working and prop is null.
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Work
{
public class Program
{
public static void Main(string[] _)
{
var folder = AppDomain.CurrentDomain.BaseDirectory;
var path = Path.Combine(folder, "Data.xml");
var xml = XElement.Load(path);
var prop = xml.Elements("Component")
.Where(c => c.Attribute("ProductId").Value == "ERDSIC")
.Descendants("Row").Where(t => t.Attribute("Name").Value == "dampersize")
.Select(v => v.Attribute("Value").Value).FirstOrDefault();
Console.WriteLine("Result: " + prop);
Console.ReadKey();
}
}
}
How to parse XElement as case insensitive ?
Here is my code:
private void GetMyLayer(XElement myElement)
{
Layer layer = new Layer();
foreach (var myItem in myElement.Descendants("layeritem"))
{
if (myItem.Element("HyperLinkFields") != null)
layer.ClickableHyperLinkFields = gisItem.Element("HyperLinkFields").Value.Split(',');
}
}
This is working fine when myItem contains a field called HyperLinkFields, but when the field name is HyperlinkFields can't figure out how to do it as case insensitive manner.
Xml is case sensitive, one could have element with same name but different case, which is perfectly valid.
If you read the documentation, Element method returns first (in document order) child element with the specified XName, so you could play with custom code and achieve the same behavior.
var element = myItem.Elements()
.FirstOrDefault(x=>x.Name.LocalName.Equals(searchstring, StringComparison.OrdinalIgnoreCase));
if(element != null)
{
// Your logic
//layer.ClickableHyperLinkFields = element.Value.Split(',');
}
Try lambda expression:
var yourItems = myItem.Elements().Where( e => e.Name.LocalName.ToString().ToLowerInvariant() == "HyperLinkFields".ToLowerInvariant() );
if(yourItems.Count() > 0) {
//do what you want here...
}
Excuse the VB. This finds elements regardless of name casing. Note that it finds all level3 elements.
Dim someXML As XElement
' someXML = XElement.Load("path here") 'to load from file / uri
' for testing
someXML = <root>
<level1 num="1">
<level2 num="1">
<LeveL3 num="1">l3 one</LeveL3>
<level3 num="2">l3 two</level3>
</level2>
<level2 num="2">
<lEVEl3 num="3">l3 one</lEVEl3>
</level2>
</level1>
<level1 num="1">
<level2 num="2">
<LEVel3 num="3">l3 one</LEVel3>
</level2>
</level1>
</root>
For Each xe As XElement In someXML...<level2>.Elements
If xe.Name.LocalName.ToLower.Equals("level3") Then
xe.Value = "found" ' just to show that ALL were found
End If
Next
I'm trying to audit some XML that is used in a bespoke piece of software. Im able to detect changes in identical structures using 'XNode.DeepEquals' and then adding an extra attribute to the elements that have changed so I can highlight them.
My problem is that, when the structure does change this methodology fails. ( I'm enumerating over both XElements at the same time performing a DeepEquals, if they are not equal - recursively calling the same method to filter out where the exact changes occurr )
Obviously this now falls apart when I'm enumerating and the nodes being compared are not the same. See Below Sample:
Before
<?xml version="1.0" encoding="utf-16"?>
<Prices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Price default="true">
<Expression operator="Addition">
<LeftOperand>
<AttributeValue field="ccx_bandwidth" />
</LeftOperand>
<RightOperand>
<Constant value="10" type="Integer" />
</RightOperand>
</Expression>
</Price>
<Price default="false">
<Expression operator="Addition">
<LeftOperand>
<AttributeValue field="ccx_bandwidth" />
</LeftOperand>
<RightOperand>
<Constant value="99" type="Integer" />
</RightOperand>
</Expression>
</Price>
<RollupChildren />
After
<?xml version="1.0" encoding="utf-16"?>
<Prices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Price default="true">
<Expression operator="Addition">
<LeftOperand>
<AttributeValue field="ccx_bandwidth" />
</LeftOperand>
<RightOperand>
<Constant value="10" type="Integer" />
</RightOperand>
</Expression>
</Price>
<RollupChildren />
So you can see that the latter Price Node has been removed and I need to show this change.
At the moment I have access to both pieces of xml and modify them on load of the audit application with an 'auditchanged' attribute which in my silverlight app i bind the background too with a converter.
I'd been playing around with Linq to Xml and looking at joining the two XElements in a query but wasn't sure how to proceed.
Ideally what I would like to do is merge the two XElements together but add a seperate attribute depending on if it's added or removed which i can then bind to with a converter to say highlight in red or green appropriately.
Does anyone have any bright ideas on this one? ( I'd been looking at XmlDiff however I can't use that in Silverlight, I don't think? )
I have a generic differ class in the codeblocks library http://codeblocks.codeplex.com
Loading your XML documents and treating each document as an IEnumerable (flattened XML tree) should allow you to use the differ as shown here: http://codeblocks.codeplex.com/wikipage?title=Differ%20Sample&referringTitle=Home
Here's the source code for differ.cs: http://codeblocks.codeplex.com/SourceControl/changeset/view/96119#1887406
Diff prototype is:
static IEnumerable<DiffEntry> Diff(IEnumerable<T> oldData, IEnumerable<T> newData, Comparison<T> identity, Comparison<T> different)
The important part here is the descendants query. It turns every element in the first doc in a list of its ancestors, where every item contains the name of the element and it's index among its siblings of the same name. I think this can be somehow used for joining, though I have no idea how to do full outer join with linq. So instead i just use these lists to find elements in the second document, and then depending on the result, probably mark it as either deleted or changed.
var doc = XDocument.Load(in_A);
var doc2 = XDocument.Load(in_B);
var descendants = doc.Descendants().Select(d =>
d.AncestorsAndSelf().Reverse().Select(el =>
new {idx = el.ElementsBeforeSelf(el.Name).Count(), el, name = el.Name}).ToList());
foreach (var list in descendants) {
XContainer el2 = doc2;
var el = list.Last().el;
foreach (var item in list) {
if (el2 == null) break;
el2 = el2.Elements(item.name).Skip(item.idx).FirstOrDefault();
}
string changed = "";
if (el2 == null) changed += " deleted";
else {
var el2e = el2 as XElement;
if (el2e.Attributes().Select(a => new { a.Name, a.Value })
.Except(el.Attributes().Select(a => new { a.Name, a.Value })).Count() > 0) {
changed += " attributes";
}
if (!el2e.HasElements && el2e.Value != el.Value) {
changed += " value";
}
el2e.SetAttributeValue("found", "found");
}
if (changed != "") el.SetAttributeValue("changed", changed.Trim());
}
doc.Save(out_A);
doc2.Save(out_B);
Is there a way to get the innertext of a node when the node is inside a collection
Currently i have this
Collection<string> DependentNodes = new Collection<string>();
foreach (XmlNode node in nodes)
{
for (int i = 0; i < node.ChildNodes.Count; i++)
{
DependentNodes.Add(node.ChildNodes[i].InnerXml);
//the reason i'm using InnerXml is that it will return all the child node of testfixture in one single line,then we can find the category & check if there's dependson
}
}
string selectedtestcase = "abc_somewords";
foreach (string s in DependentNodes)
{
if(s.Contains(selectedtestcase))
{
MessageBox.Show("aaa");
}
}
When i debug string s or the index has this inside of it[in a single line]
<testfixture name="1" description="a">
<categories>
<category>abc_somewords</category>
</categories>
<test name="a" description="a">
<dependencies>
<dependson typename="dependsonthis" />
</dependencies>
</test>
</testfixture>
What i'm trying to do is when we reach "testfixture 1" it will find "abc_somewords" & search the "dependson typename"node(if any) and get the "typename"(which is "dependonthis").
Could you use linq to xml. Something like the below might be a decent start
xml.Elements("categories").Where(x => x.Element("category").Value.Contains(selectedtestcase));
This is off the top of my head so might will need refining
P.S. Use XElement.Load or XElement.Parse to get your xml into XElements
Since you already working with XmlNode you could use a XPath expression to select the desired textfixture node, and select the dependency value:
XmlDocument doc = // ...
XmlNode node = doc.SelectSingleNode("//testfixture[contains(categories/category, \"abc\")]/test/dependencies/dependson/");
if (node != null)
{
MessageBox.Show(node.Attributes["typename"]);
}
This selects the dependson node which belongs to a testfixture node with a category containing "abc". node.Attributes["typename"] will return the value of the typename attribute.
Edited:
Updated XPath expression to the more specific question information
Assumptions
As you are looping in your code and wanting to create a collection I'm assuming the actual Xml File has several testfixture nodes inside such as the below assumed example:
<root>
<testfixture name="1" description="a">
<categories>
<category>abc_somewords</category>
</categories>
<test name="a" description="a">
<dependencies>
<dependson typename="dependsonthis" />
</dependencies>
</test>
</testfixture>
<testfixture name="2" description="a">
<categories>
<category>another_value</category>
</categories>
<test name="b" description="a">
<dependencies>
<dependson typename="secondentry" />
</dependencies>
</test>
</testfixture>
<testfixture name="3" description="a">
<categories>
<category>abc_somewords</category>
</categories>
<test name="c" description="a">
<dependencies>
<dependson typename="thirdentry" />
</dependencies>
</test>
</testfixture>
</root>
The Code using Linq to Xml
To use Linq you must reference the following name spaces:
using System.Linq;
using System.Xml.Linq;
Using Linq To Xml on the above assumed xml file structure would look like this:
// To Load Xml Content from File.
XDocument doc1 = XDocument.Load(#"C:\MyXml.xml");
Collection<string> DependentNodes = new Collection<string>();
var results =
doc1.Root.Elements("testfixture")
.Where(x => x.Element("categories").Element("category").Value.Contains("abc_somewords"))
.Elements("test").Elements("dependencies").Elements("dependson").Attributes("typename").ToArray();
foreach (XAttribute attribute in results)
{
DependentNodes.Add(attribute.Value.Trim());
}
Result
The resulting Collection will contain the following:
As you can see, only the text of the typename attribute has been extracted where the dependson nodes where in a testfixture node which contained a category node with the value of abc_somewords.
Additional Notes
If you read the xml from a string you can also use this:
// To Load Xml Content from a string.
XDocument doc = XDocument.Parse(myXml);
If your complete Xml structure is different, feel free to post it and I change the code to match.
Have Fun.
I don't know what is "nodes" you are using.
Here is code with your requirement(What I understood).
Collection<XmlNode> DependentNodes = new Collection<XmlNode>();
XmlDocument xDoc = new XmlDocument();
xDoc.Load(#"Path_Of_Your_xml");
foreach (XmlNode node in xDoc.SelectNodes("testfixture")) // Here I am accessing only root node. Give Xpath if ur requrement is changed
{
for (int i = 0; i < node.ChildNodes.Count; i++)
{
DependentNodes.Add(node.ChildNodes[i]);
}
}
string selectedtestcase = "abc_somewords";
foreach (var s in DependentNodes)
{
if (s.InnerText.Contains(selectedtestcase))
{
Console.Write("aaa");
}
}
using System;
using System.Xml;
namespace ConsoleApplication6
{
class Program
{
private const string XML = "<testfixture name=\"1\" description=\"a\">" +
"<categories>" +
"<category>abc_somewords</category>" +
"</categories>" +
"<test name=\"a\" description=\"a\">" +
"<dependencies>" +
"<dependson typename=\"dependsonthis\" />" +
"</dependencies>" +
"</test>" +
"</testfixture>";
static void Main(string[] args)
{
var document = new XmlDocument();
document.LoadXml(XML);
var testfixture = document.SelectSingleNode("//testfixture[#name = 1]");
var category = testfixture.SelectSingleNode(".//category[contains(text(), 'abc_somewords')]");
if(category != null)
{
var depends = testfixture.SelectSingleNode("//dependson");
Console.Out.WriteLine(depends.Attributes["typename"].Value);
}
Console.ReadKey();
}
}
}
Output: dependsonthis