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
Related
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.
Is there a way to improve upon this? I feel like I have way to many loops going on.
The code builds a string with domain names in it from an XML document. Depending on whether the domain name in the XML doc is in the hostSW (host Starts With), hostCN (host Contains) or hostEW (host EndsWith) element depends on whether I need to append a * to the end, beginning+end, or beginning of the value respectively.
I used a Hashset as a way of making sure that there is no duplication.
var startWith = xdoc.Root
.Descendants("Test")
.Elements("hostSW")
.ToList();
var contains = xdoc.Root
.Descendants("Test")
.Elements("hostCN")
.ToList();
var endsWith = xdoc.Root
.Descendants("Test")
.Elements("hostEW")
.ToList();
HashSet<string> domains = new HashSet<string>(); //use hashset so we don't duplicate results
foreach (XElement test in startWith)
{
domains.Add(test.Value.ToString() + "*");
}
foreach (XElement test in contains)
{
domains.Add("*" + test.Value.ToString() + "*");
}
foreach (XElement test in endsWith)
{
domains.Add("*" + test.Value.ToString());
}
string out = "BEGIN FILE ";
foreach (string domain in domains.ToArray())
{
out += "BEGIN DOMAIN ";
out += domain;
out += " END DOMAIN";
}
out += " END FILE;
return out;
XML file
<Tests>
<Test>
<hostSW>startsWith1</hostSW>
</Test>
<Test>
<hostSW>startsWith2</hostSW>
</Test>
<Test>
<hostCN>contains1</hostCN>
</Test>
<Test>
<hostEW>endsWith1</hostEW>
</Test>
</Tests>
Assuming an XML structure like this
<Root>
<Test>
<hostSW>startsWith1</hostSW>
</Test>
<Test>
<hostCN>contains1</hostCN>
</Test>
<Test>
<hostCN>contains2</hostCN>
</Test>
<Test>
<hostEW>endsWith1</hostEW>
</Test>
<Test>
<hostEW>endsWith2</hostEW>
</Test>
<Test>
<hostEW>endsWith3</hostEW>
</Test>
</Root>
I got all the domains correctly and only iterated 6 times with this code
// Loop each of the elements one time without filtering element name
foreach (var test in xDoc.Root.Descendants("Test").Elements())
{
// Switch on the name of the element.
switch (test.Name.LocalName)
{
case "hostSW": { domains.Add(test.Value + "*"); } break;
case "hostCN": { domains.Add("*" + test.Value + "*"); } break;
case "hostEW": { domains.Add("*" + test.Value); } break;
}
// For any other elements we iterate just do nothing
}
Using Linq to XML you can do:
var t = xml.Root.Descendants("Test")
.Elements()
.Where(x => x.Name == "hostSW" || x.Name == "hostCN" || x.Name == "hostEW")
.Select(x => (x.Name == "hostSW") ?
$"{x.Value}*"
:
(x.Name == "hostCN" ?
$"*{x.Value}*"
:
$"*{x.Value}"));
If your <Test> elements don't contain anything but <hostSW>, <hostCN> or <hostEW> then you can omit the Where.
Demo here
Have you tried working with SelectSingleNode or the SelectNodes method with the XmlNode class? I know you are trying to avoid loops, but for this scenario you should be receiving a time complexity of O(n), as long as we know the root(parent nodes) stay the same.
It looks like your XML file has nodes containing the inner text you need, so you could do something like this:
foreach (XmlNode node in xmlDoc.SelectNodes($"//Test/Test"))
{
switch (node.Name)
{
case "hostSW":
var newInnerText = node.InnerText + '*';
//do something
break;
case "hostCN":
var newInnerText = node.InnerText + '*';
//do something
break;
case "hostEW":
//do something
break;
default:
//do something
break;
}
}
Here's yet another way. This uses a LINQ Select instead of the loop, and uses a Dictionary instead of the switch / ternary tree. It also leverages a substitution mechanism: in this case I used string.Format but many others including string.Replace would be similarly appropriate. It all depends what you're going for. I usually go for reasonable performance with economical syntax, which usually scores good points for ease of maintenance.
static Dictionary<string, string> dict = new Dictionary<string, string>() {
{ "hostSW", "{0}*" },
{ "hostCN", "*{0}*" },
{ "hostEW", "*{0}" },
};
static IEnumerable<string> WildcardStringsFromXML(XDocument xdoc)
{
return xdoc.Root.Descendants("Test").Elements()
.Select(item => string.Format(dict[item.Name.LocalName], item.Value));
}
Can anybody please tell me why this Xdocument query is returning null when there are elements / attributes that i'm trying to grab.
I'm trying to get a collection of the <version> elements so i can read the attributes of them. Example XML:
<dmodule>
<idstatus>
<dmaddres>
<dmc>Some DMC</dmc>
<dmtitle><techname>My techname</techname><infoname>My infoname</infoname></dmtitle>
<issno issno="004" type="revised">
<issdate year="2016" month="11" day="30"></dmaddres>
<status>
<security class="2">
<rpc>RPC1</rpc>
<orig>ORIG1</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></status>
</idstatus>
<dmodule>
And this is how i'm trying to get the <version> elements:
XDocument doc = XDocument.Load(sgmlReader);
List<string> applicabilityList = null;
//this doesn't work
//var applics = doc.XPathSelectElements("dmodule/idstatus/status/applic/model/version");
//nor does this
var applics = doc.Descendants("idstatus").Descendants("applic").Elements("version");
foreach (var applic in applics)
{
string applicVersion = applic.Attribute("version").ToString();
applicabilityList.Add(applicVersion);
}
return applicabilityList;
Either query as shown above returns no results. Cleary a silly mistake in my query but i'm out of practice.
That's because you are missing the model element.
...
<applic>
<model model="2093">
<version version="BASE"></
...
If all you are interested are the version elements you can simply do:
var versions = doc.Descendants("version");
This is the working code
var applics = doc.Descendants("dmodule")
.Descendants("idstatus")
.Descendants("status")
.Descendants("security")
.Descendants("applic")
.Descendants("model")
.Elements("version");
I have to extract values belonging to certain elements in an XML file and this is what I ended up with.
XDocument doc = XDocument.Load("request.xml");
var year = (string)doc.Descendants("year").FirstOrDefault();
var id = (string)doc.Descendants("id").FirstOrDefault();
I'm guessing that for each statement I'm iterating through the entire file looking for the first occurrence of the element called year/id. Is this the correct way to do this? It seems like there has to be a way where one would avoid unnecessary iterations. I know what I'm looking for and I know that the elements are going to be there even if the values may be null.
I'm thinking in the lines of a select statement with both "year" and "id" as conditions.
For clearance, I'm looking for certain elements and their respective values. There'll most likely be multiple occurrences of the same element but FirstOrDefault() is fine for that.
Further clarification:
As requested by the legend Jon Skeet, I'll try to clarify further. The XML document contains fields such as <year>2015</year> and <id>123032</id> and I need the values. I know which elements I'm looking for, and that they're going to be there. In the sample XML below, I would like to get 2015, The Emperor, something and 30.
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<documents xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<make>Apple</make>
<year>2015</year>
<customer>
<name>The Emperor</name>
<level2>
<information>something</information>
</level2>
<age>30</age>
</customer>
A code that doesn't parse the whole xml twice would be like:
XDocument doc = XDocument.Load("request.xml");
string year = null;
string id = null;
bool yearFound = false, idFound = false;
foreach (XElement ele in doc.Descendants())
{
if (!yearFound && ele.Name == "year")
{
year = (string)ele;
yearFound = true;
}
else if (!idFound && ele.Name == "id")
{
id = (string)ele;
idFound = true;
}
if (yearFound && idFound)
{
break;
}
}
As you can see you are trading lines of code for speed :-) I do feel the code is still quite readable.
if you really need to optimize up to the last line of code, you could put the names of the elements in two variables (because otherwise there will be many temporary XName creation)
// before the foreach
XName yearName = "year";
XName idName = "id";
and then
if (!yearFound && ele.Name == yearName)
...
if (!idFound && ele.Name == idName)
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