XPathNavigator with xpath gives wrong node back - c#

I try to get some specific values from an xml config. See example below.
<?xml version="1.0" encoding="utf-8"?>
<ExcelConfig>
<ExcelDocument name="Customer" type="flat">
<IdentityColumn>
<Column name="Id" />
</IdentityColumn>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
<OrColumns mandatory="true">
<Column name="PostalCode" mandatory="false" />
<Column name="PostalCode2" mandatory="false" />
</OrColumns>
</Validate>
</ExcelDocument>
<ExcelDocument name="Company" type="flat">
<IdentityColumn>
<Column name="Id" />
</IdentityColumn>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
<OrColumns mandatory="true">
<Column name="PostalCode" mandatory="false" />
<Column name="PostalCode2" mandatory="false" />
</OrColumns>
</Validate>
</ExcelDocument>
<ExcelDocument name="SomeOtherType" type="block">
<IdentityBlock>
<Column name="Period" col="A" />
<Column name="Period2" col="B" />
</IdentityBlock>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
</Validate>
</ExcelDocument>
</ExcelConfig>
I use the following code to get some information from the excel file.
"ValidationConfiguration" is the string with the previous configuration.
//Get Different NodeTypes of Excel documents
List<XPathNavigator> types = XmlHelper.GetNodeTypes(validationConfiguration, "/ExcelConfig/ExcelDocument");
List<XPathNavigator> flatTypes = XmlHelper.GetNodeTypes(validationConfiguration,
"//ExcelConfig/ExcelDocument[#type='flat']");
List<XPathNavigator> blockTypes = XmlHelper.GetNodeTypes(validationConfiguration,
"//ExcelConfig/ExcelDocument[#type='block']");
//First we check if the file is from the flat type and get the IdentityColumns
List<XPathNavigator> identityColumnsNode = XmlHelper.GetNodeTypes(validationConfiguration, "//ExcelConfig/ExcelDocument[#type='flat']/IdentityColumn");
You can find the XmlHelper class below.
public static class XmlHelper
{
public static List<XPathNavigator> GetNodeTypes(string xmlConfiguration,string xPath)
{
XPathDocument doc = new XPathDocument(new StringReader(xmlConfiguration));
XPathNavigator nav = doc.CreateNavigator();
XPathExpression expr = nav.Compile(xPath);
List<XPathNavigator> elements = new List<XPathNavigator>();
foreach (XPathNavigator node in nav.Select(expr))
{
elements.Add(node);
}
return elements;
}
public static List<string> GetIdentityColumnNames(List<XPathNavigator> xPathNavigators)
{
List<string> identityColumns = new List<string>();
foreach (XPathNavigator xPathNavigator in xPathNavigators)
{
foreach (XPathNavigator test in xPathNavigator.Select("//Column"))
{
identityColumns.Add(test.GetAttribute("name", ""));
}
}
return identityColumns;
}
}
Now i want to do the following. I selected the identityColumnsNodes(they contains the IdentityColumn from the exceldocuments that have the flat type).
The i get for al that types the colums. But when i try that, i get all columns back from the whole file. He don't only the items from the node that i use.
foreach (XPathNavigator identityColumNode in identityColumnsNode)
{
List<string> identityColumns = XmlHelper.GetIdentityColumnNames(identityColumnsNode);
}
The second problem/thing i want to do --> the best way to select the right validate node from the specific file. With the identityColumns (that i get back and my list of HeaderRow Cells i know what file it is. But how can i select that validate node?
Or are their better methods to do this stuff?

Related

C# XML delete everything except specific element and its children

I have this XML file:
<XtraSerializer version="1.0" application="View">
<property name="Columns" iskey="true" value="23">
<property name="Item23" isnull="true" iskey="true">
<property name="Name">colworkspace</property>
<property name="Width">75</property>
<property name="MinWidth">20</property>
<property name="MaxWidth">0</property>
</property>
</property>
<property name="FormatRules" iskey="true" value="1">
<property name="Item1" isnull="true" iskey="true">
<property name="ColumnName">colid</property>
<property name="Name">Format0</property>
<property name="RuleType">#FormatConditionRuleExpression</property>
<property name="Rule" isnull="true" iskey="true">
<property name="Expression">[id] > 1L</property>
<property name="Appearance" isnull="true" iskey="true">
<property name="Options" isnull="true" iskey="true">
<property name="UseForeColor">true</property>
</property>
<property name="ForeColor">195, 214, 155</property>
</property>
</property>
</property>
</property>
</XtraSerializer>
It has two properties, Columns and FormatRules. I want to delete the Columns property and keep the FormatRules property. What I have done is creating a method that deletes all elements which does not have name = FormatRules but that will delete all the children of the FormatRules property too, which I don't want. This is my code:
XDocument doc = XDocument.Load(path);
IEnumerable<XElement> element = from node in doc.Descendants("property")
let attr = node.Attribute("name")
where attr != null && attr.Value != "FormatRules"
select node;
element.ToList().ForEach(x => x.Remove());
doc.Save(path);
This will result in this XML file:
<XtraSerializer version="1.0" application="View">
<property name="FormatRules" iskey="true" value="1">
</XtraSerializer>
//IEnumerable<XElement> element = from node in doc.Descendants("property")
IEnumerable<XElement> element = from node in doc.Root.Elements("property")

XML read and getting values

How can I get a this: val="icon.weapon_small_sword_i00"/> from XML file by giving a itemId. I managed to do something like this but besides of choosing right itemId from file I dont know how to get the value I mentioned above.
Thats a code I have:
int ItemId = 15;
XmlTextReader reader = new XmlTextReader(#"D:\L2Eq\xml\items");
XmlNodeType type;
while (reader.Read())
{
type = reader.NodeType;
if (type == XmlNodeType.Element)
{
if (reader.Name == "item")
{
if (Int32.Parse(reader.GetAttribute(0)) == ItemId)
{
Console.WriteLine(reader.GetAttribute(0));
}
}
}
}
And thats how XML file looks like:
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="items.xsd">
<item id="1" type="Weapon" name="Short Sword">
<set name="icon" val="icon.weapon_small_sword_i00" />
<set name="default_action" val="equip" />
<set name="weapon_type" val="sword" />
<set name="bodypart" val="rhand" />
<set name="random_damage" val="10" />
<set name="attack_range" val="40" />
<for>
<set order="0x08" stat="pAtk" val="8" />
<set order="0x08" stat="mAtk" val="6" />
<set order="0x08" stat="rCrit" val="8" />
<set order="0x08" stat="pAtkSpd" val="379" />
</for>
</item>
<item id="2" type="Weapon" name="Long Sword">
<set name="icon" val="icon.weapon_long_sword_i00" />
<set name="default_action" val="equip" />
<set name="weapon_type" val="sword" />
<set name="bodypart" val="rhand" />
<set name="random_damage" val="10" />
<set name="attack_range" val="40" />
<for>
<set order="0x08" stat="pAtk" val="24" />
<set order="0x08" stat="mAtk" val="17" />
<set order="0x08" stat="rCrit" val="8" />
<set order="0x08" stat="pAtkSpd" val="379" />
</for>
</item>
</list>
XmlTextReader is a very clumsy way to deal with XML; I'd never use it. The old System.Xml.XmlDocument API is a much better choice:
var findID = "1";
string iconValue = null;
var xdoc = new System.Xml.XmlDocument();
xdoc.Load(#"D:\L2Eq\xml\items");
iconValue = xdoc.SelectSingleNode("/list/item[#id=" + findID + "]/set[#name='icon']/#val")?.Value;
Or you could use the shiny new LINQ to XML classes:
var doc = XDocument.Load(#"D:\L2Eq\xml\items");
iconValue = doc.Descendants("item")
.Where(d => d.Attribute("id")?.Value == findID)
.Descendants("set")
.Where(x => x.Attribute("name")?.Value == "icon")
.FirstOrDefault()?.Attribute("val")?.Value;

C# Parsing XML and XElement

I have this code. Also here is a sample of the XML. I apologize for any confusion
<object type="node" >
<property name="id" value="1" />
<property name="name" value="ossvc06_node1" />
<property name="port_id" value="50050768014062AC" />
<property name="port_status" value="active" />
<property name="port_speed" value="4Gb" />
<property name="port_id" value="50050768013062AC" />
<property name="port_status" value="active" />
<property name="port_speed" value="4Gb" />
<property name="port_id" value="50050768011062AC" />
<property name="port_status" value="active" />
<property name="port_speed" value="4Gb" />
<property name="port_id" value="50050768012062AC" />
<property name="port_status" value="active" />
<property name="port_speed" value="4Gb" />
<property name="hardware" value="8G4" />
<property name="iscsi_name" value="iqn.1986-03.com.ibm:2145.ossvc06.ossvc06node1" />
<property name="iscsi_alias" value="" />
<property name="failover_active" value="no" />
<property name="failover_name" value="ossvc06_node2" />
<property name="failover_iscsi_name" value="iqn.1986- .com.ibm:2145.ossvc06.ossvc06node2" />
<property name="failover_iscsi_alias" value="" />
<property name="front_panel_id" value="115286" />
</object>
In the input file there are two of these objects of type "Node" each with different values for the property tags.
In this code I am looking for all the objects of type "node" in the incoming XML. There are 2 of them. The 'var nodes' statement evaluates correctly. In the debugger I can see two XElements with what appears to be the proper type and elements in the element list. However, the statement that gets the elements and assigns them to a list has ChildElements from both of the objects of type "node" that are in the XML and I am not sure why.
//load the input file
XDocument xdoc = XDocument.Load(_InputFile);
//get the object of type 'node'
//this code gives the results expected
// in the debugger each XElement appears to have the proper value and childElements
var nodes = from node in xdoc.Descendants("object")
where node.Attribute("type").Value == "node"
select node;
foreach (XElement nodelement in nodes)
{
// problem happens here, the child elements from both nodes get assigned to the list
List<XElement> nodeles = nodelement.Elements().ToList();
Node node = NodeFactory(nodelement);
// now assign the node to the correct IO group
var iogrp = SVCClusters[0].IOGroups.Where(io => io.Name == node.IOGroupName);
if (iogrp.FirstOrDefault().FirstNode == null) { iogrp.FirstOrDefault().FirstNode = node; }
else { iogrp.FirstOrDefault().SecondNode = node; }
}
Can you run this in a console app and tell me what you get
static void Main(string[] args)
{
XDocument xdoc = XDocument.Parse("<root><object type=\"node\" ><property name=\"id\" value=\"1\" /><property name=\"name\" value=\"ossvc06_node1\" /></object><object type=\"node\" ><property name=\"id\" value=\"2\" /><property name=\"name\" value=\"ossvc06_node2\" /></object></root>");
var nodes = xdoc.Descendants("object").Where(node => node.Attribute("type").Value == "node").ToList();
foreach (XElement nodelement in nodes)
{
List<XElement> nodeles = nodelement.Elements().ToList();
foreach (var node in nodeles)
Console.WriteLine(node);
}
}
You should have 4 rows output to the console
Edit---
Original issue was that an XPath expression "//property[#name='port_id']" was being used. This queries from the document root, not from the current node.
Change the XPath to be either "property[#name='port_id'] or ".//property[#name='port_id']"

Select from XML, Build datastructure

I'm trying to build a datastructure based on xml content.
The structure looks like that:
Dictionary<string, List<Dictionary<string, string>>>
The XML looks like that:
<Table name="testTable">
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
</Table>
<Table name="testTable01">
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
</Table>
It should result in something like that:
Dictionary<tableName, List<Dictionary<columnName, columnValue>>>
It's no problem to do that with some nested foreach-loops, but I'm searching for a way to do that "in one line" with the LINQ extension methods. How could I do that?
Sounds like you want something like:
var tables = doc.Descendants("Table")
.ToDictionary(t => (string) t.Attribute("name"),
t => ExtractRowsFromTable(t));
...
private static List<Dictionary<string, string>> ExtractRowsFromTable(XElement table)
{
return table.Elements("Row")
.Select(row => row.Elements("Column")
.ToDictionary(c => (string) c.Attribute("name"),
c => (string) c.Attribute("value"))
.ToList();
}
You could do all of this in one line, basically inlining ExtractRowsFromTable - but I really wouldn't.
string xml =
#"<Root>
<Table name=""testTable"">
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
</Table>
<Table name=""testTable2"">
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
</Table>
</Root>";
XDocument xDoc = XDocument.Parse(xml);
var table = xDoc
.Descendants("Table")
.Select(t => new
{
Name = t.Attribute("name").Value,
Rows = t.Descendants("Row")
.Select(r=> r.Descendants("Column")
.ToDictionary(c=>c.Attribute("name").Value,
c=>c.Attribute("value").Value))
.ToList()
})
.ToList();

Build List<List<XElement>> from XML

I have an XML that is laid out to be reformatted into nested HTML table headers. I am working on getting each tier of the XML document into it's own list. For example:
<column name="Total" size="0">
<column name="Users" size="0" />
</column>
<column name="Date" size="0" />
<column name="Unique" size="0">
<column name="Clicks" size="0">
<column name="RC" size="0" />
<column name="CB" size="0" />
</column>
</column>
From this example, columns "Total", "Date", and "Unique" should be in the first list. Columns "Users" and "Clicks" should be in the second list. And, columns "RC" and "CB" should be in the third list. This should be accomplished using recursion to make the method completely dynamic. Any help is greatly appreciated.
Here you go:
XElement root = XElement.Parse(#"
<doc>
<column1>
<column2 />
</column1>
<column3 />
<column4>
<column5>
<column6 />
<column7 />
</column5>
</column4>
</doc>");
List<List<XElement>> outerList = new List<List<XElement>>();
List<XElement> innerList = root.Elements().ToList();
while (innerList.Any())
{
outerList.Add(innerList);
innerList = innerList.SelectMany(element => element.Elements()).ToList();
}
Edit: If you want to strip ancestor XElement instances of their descendants within your list, then you could use the following:
XElement root = XElement.Parse(#"
<table>
<column name=""Total"" size=""0"">
<column name=""Users"" size=""0"" />
</column>
<column name=""Date"" size=""0"" />
<column name=""Unique"" size=""0"">
<column name=""Clicks"" size=""0"">
<column name=""RC"" size=""0"" />
<column name=""CB"" size=""0"" />
</column>
</column>
</table>");
List<List<XElement>> outerList = new List<List<XElement>>();
IEnumerable<XElement> innerList = root.Elements();
while (innerList.Any())
{
outerList.Add(innerList.Select(e => new XElement(e.Name, e.Attributes())).ToList());
innerList = innerList.SelectMany(element => element.Elements());
}
Note: For the record, your intuition that you should use recursion was correct. However, it is also well known that any recursive function can be converted to an iteration, typically by simulating the stack. Sometimes, this leads to bloated code; however, other times, the conversion lends itself naturally. In your case, if you were to recurse, your recursive parameter would have been the immediate children of the set of elements currently being considered – which already happens to be available in innerList, thus allowing us to use the innerList = innerList.<SequenceOperation> trick to substitute the recursion.

Categories