Select number of data based of a specific node - c#

I have the following piece of code
XmlDocument docu = new XmlDocument();
docu.Load(file);
XmlNodeList lst = docu.GetElementsByTagName("name");
foreach (XmlNode n in lst)
{
string text = n.InnerText;
var types = doc.Element("program").Element("program-function").Element("function").Descendants("type").Where(x => x.Value == text).Select(c => c.Value).ToArray();
}
my xml is as follows
<program>
<program-function>
<function>
<name>add</name>
<return-type>double</return-type>
<params>
<type>double</type>
<type-value>a</type-value>
<type>double</type>
<type-value>b</type-value>
<type>string</type>
<type-value>c</type-value>
</params>
<body> return a + b + c; </body>
</function>
<function>
<name>test</name>
<return-type>int</return-type>
<params>
<type>double</type>
<type-value>a</type-value>
<type>double</type>
<type-value>b</type-value>
</params>
<body> return a + b; </body>
</function>
</program-function>
</program>
i need to be able to get the number of <type> for each <name>
result for add should be 3 = types.count() = 3
result for test should be 2 = types.count() = 2
any advice?
EDIT : If i want to retrieve each value inside types? ie. add should contain a,b,c and test should contain a,b. Would love to store it in an array for easier retrieval

How about using Linq to Xml
var xDoc = XDocument.Parse(xml);
var functions = xDoc.Descendants("function")
.Select(f => new
{
Name = f.Element("name").Value,
Types = f.Descendants("type").Select(t=>t.Value).ToList(),
//Types = f.Descendants("type").Count()
TypeValues = f.Descendants("type-value").Select(t=>t.Value).ToList()
})
.ToList();

Try this:
XDocument doc = XDocument.Load(your file);
var vals = doc.Element("program").Element("program-function").Elements("function");
var result = vals.Select(i =>
new { name = i.Element("name"),
count = i.Elements("type").Count() }

Related

xml parsing - code refactoring issue

I have the following xml:
<?xml version="1.0" encoding="utf-8"?>
<RootData>
<PassResult>
<FirstOne>P1</FirstOne>
<SecondOne>P2</SecondOne>
<IsMale>false</IsMale>
</PassResult>
<TestResult>
<MarkOne>100</MarkOne>
<MarkTwo>50</MarkTwo>
<Slope>30</Slope>
</TestResult>
<ToneTestResult>
<TotalTime>2</TotalTime>
<CorrectPercentage>90</CorrectPercentage>
</ToneTestResult>
<QuestionnaireResult Version="1">
<Question Id="50">
<Answer Id="B" />
</Question>
<Question Id="60">
<Answer Id="A" />
</Question>
</QuestionnaireResult>
</RootData>
I have the following code which is not at all looking a good one. Lots of repeatative link queries.
How can I refactor this code in a more structured way to fill "OutputData" object. I do not want to change it to XmlSerializer now :-(.
Sample code:
// Gets the root element decendants
var elementRootData = xDocument.Descendants("RootData");
var xElements = elementRootData as IList<XElement> ?? elementRootData.ToList();
// Read first leaf node "ProfileResult"
var passResult = from xElement in xElements.Descendants("PassResult")
select new
{
FirstOne = xElement.Element("FirstOne").GetValue(),
SecondOne = xElement.Element("SecondOne").GetValue(),
IsMale = xElement.Element("IsMale").GetValue()
};
// Read second leaf note
var testResult = from xElement in xElements.Descendants("TestResult")
select new
{
MarkOne = xElement.Element("MarkOne").GetValue(),
MarkTwo = xElement.Element("MarkTwo").GetValue(),
Slope = xElement.Element("Slope").GetValue()
};
// Update OutputData object
var parseOutputData = new OutputData();
foreach (var result in passResult)
{
parseOutputData.FirstOne = result.FirstOne;
parseOutputData.SecondOne = result.SecondOne;
parseOutputData.IsMale = result.IsMale.Equals("True");
}
foreach (var result in testResult)
{
parseOutputData.MarkOne = double.Parse(result.MarkOne);
parseOutputData.MarkTwo = double.Parse(result.MarkTwo);
parseOutputData.Slope = double.Parse(result.Slope);
}
I have to write some more code like this to fill other elements data like ToneTestResult, QuestionnaireResult etc.
Can someone suggest with a sample code?
Best regards,
Given your XML is tiny, you probably don't have to worry too much about performance. You can just do the whole thing in one go, making use of the built in explicit conversions:
var data = new OutputData
{
FirstOne = (string) doc.Descendants("FirstOne").Single(),
SecondOne = (string) doc.Descendants("SecondOne").Single(),
IsMale = (bool) doc.Descendants("IsMale").Single(),
MarkOne = (double) doc.Descendants("MarkOne").Single(),
MarkTwo = (double) doc.Descendants("MarkTwo").Single(),
Slope = (double) doc.Descendants("Slope").Single()
};
As an aside, Descendants will never return anything implementing IList<XElement>, so you can definitely remove that.
Try XML Linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var root = doc.Elements("RootData").Select(x => new
{
passResults = x.Elements("PassResult").Select(y => new
{
firstOne = (string)y.Element("FirstOne"),
secondOne = (string)y.Element("SecondOne"),
isMale = (Boolean)y.Element("IsMale")
}).FirstOrDefault(),
testResult = x.Elements("TestResult").Select(y => new {
markOne = (int)y.Element("MarkOne"),
markTwo = (int)y.Element("MarkTwo"),
slope = (int)y.Element("Slope")
}).FirstOrDefault(),
toneTestResult = x.Elements("ToneTestResult").Select(y => new {
totalTime = (int)y.Element("TotalTime"),
correctPecentage = (int)y.Element("CorrectPercentage")
}).FirstOrDefault(),
questionnaireResult = x.Elements("QuestionnaireResult").Elements("Question").Select(y => new {
question = (int)y.Attribute("Id"),
answer = (string)y.Descendants("Answer").FirstOrDefault().Attribute("Id")
}).ToList(),
}).FirstOrDefault();
}
}
}

C# Removing entire elements from XMLDocument based on list of unwanted attribute values

I have an XML file, e.g.
<Bars1>
<Bar name='0'>245</Bar>
<Bar name='1'>180</Bar>
<Bar name='2'>120</Bar>
<Bar name='3'>60</Bar>
<Bar name='4'>0</Bar>
</Bars1>
<Bars2>
<Bar name='0'>25</Bar>
<Bar name='1'>10</Bar>
<Bar name='2'>10</Bar>
<Bar name='3'>6</Bar>
<Bar name='4'>0</Bar>
</Bars2>
<Gubbins3>
<Bar name='0'>45</Bar>
<Bar name='1'>18</Bar>
<Bar name='2'>12</Bar>
<Bar name='3'>4</Bar>
<Bar name='4'>0</Bar>
</Gubbins3>
and a List<int> notNeededBarNames, containing e.g. { 1, 3 }
I have loaded the XML file into an XmlDocument xmlDoc, and want to remove ANY "Bar" element where the attribute "name" is one of the integers from my list, regardless of where it might exist in the XML . My example is small, but in reality the document and the list could be quite large.
Is there a nice elegant approach to doing this? I can "brute force" it, but I can't help feeling there might be a better way.
Hope you can help!
Linq2Xml can make the life easier.
var notNeededBarNames = new List<int>() { 1, 3 };
var xDoc = XDocument.Load(filename);
xDoc.Descendants("Bar")
.Where(bar => notNeededBarNames.Contains( (int)bar.Attribute("name")) )
.Remove();
var newXml = xDoc.ToString();
For an xpath-based solution, you can select the nodes to remove as follows:
private static void TestXmlBars()
{
string s = #"<root>
<Bars1>...</Bars1>
<Bars2>...</Bars2>
<Gubbins3>...</Gubbins3>
</root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(s);
List<int> exclude = new List<int>{1,2};
// create comma-delimited list
string list = "," + String.Join(",", exclude) + ",";
string xpath = String.Format("/root/*/Bar[contains('{0}', concat(',', #name, ','))]", list);
XmlNodeList nodesToExclude = doc.SelectNodes(xpath);
foreach (XmlNode node in nodesToExclude)
{
node.ParentNode.RemoveChild(node);
}
Console.WriteLine(doc.OuterXml);
}
(Note that xpath 1 is constrained as it has no 'in' functionality, hence the 'contains' workaround - see XPath 1.0 to find if an element's value is in a list of values)
Something like this will work
using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
class Program
{
static void Main()
{
var notNeededBarNames = new[] {1, 3};
var elems = new[] {"Bars1", "Bars2", "Gubbins3"};
var newXDoc = new XElement("root");
var xDoc = XDocument.Load("PathToYourXmlFile");
foreach (var elem in elems)
{
newXDoc.Add(new XElement(elem));
var curElem = newXDoc.Elements(elem).Single();
string xpath = String.Format("root/{0}/Bar", elem);
var childElems = xDoc.XPathSelectElements(xpath);
foreach (var childElem in childElems)
{
bool add = true;
var nameAtt = childElem.Attribute("name");
if (nameAtt != null)
{
int val = Convert.ToInt32(nameAtt.Value);
if (notNeededBarNames.Any(x => x == val))
{
add = false;
}
}
if (add)
{
curElem.Add(childElem);
}
}
}
var newXml = newXDoc.ToString();
}
}

Xml attributes to string array

How to get all "cc" attribute values from below xml into a string array
<DD a="1" b="2" c="3">
<D aa="11" bb="22" cc="33"/>
<D aa="11" bb="22" cc="33"/>
<D aa="11" bb="22" cc="33"/>
</DD>
Cases I tried:
foreach (XmlNode xD in DD) {
XmlElement getFDD = (XmlElement)DD;
for (int x = 0; x < DD.ChildNodes.Count; x++)
{
XmlElement XmlFV = (XmlElement)DD.ChildNodes[x];
stringArr[x] = XmlFV.GetAttribute("CC");
}
}
And
for (int u = 0; u < DD.Count; u++)
{
getFDD = (XmlElement)DD[u].FirstChild;
XmlElement getFDD1 = (XmlElement)getFDD;
stringArr[u]=getFDD1.GetAttribute("cc");
}
I tried using foreach to loop through each node, i gave up trying that.
This should work for .Net 2.0
var doc = new XmlDocument();
doc.Load(fname);
List<string> list = new List<string>();
foreach(XmlNode node in doc.GetElementsByTagName("D"))
{
list.Add(node.Attributes["cc"].Value);
}
You can do it with LINQ2XML:
const string xml = #"
<DD a=""1"" b=""2"" c=""3"">
<D aa=""11"" bb=""22"" cc=""33""/>
<D aa=""11"" bb=""22"" cc=""33""/>
<D aa=""11"" bb=""22"" cc=""33""/>
</DD>";
var doc = XDocument.Parse(xml);
var res = doc.Element("DD") // Get the root element DD
.Elements("D") // Extract all sub-elements D
.Select(e => e.Attribute("cc").Value) // Extract attribute cc
.ToList();
Here is how to do it without LINQ2XML:
XmlReader r = XmlReader.Create(new StringReader(xml));
IList<string> res = new List<string>();
while (r.Read()) {
if (r.IsStartElement("D")) {
res.Add(r.GetAttribute("cc"));
}
}
Use Linq2Xml
XElement doc=XElement.Load(yourXmlPath);
String[] attr=doc.Elements("D")
.Select(x=>(String)x.Attribute("cc"))
.ToArray();

XMLString needs to change the format

my xml DATA IS like:(This is xmlstring not an xmlfile and i need to transform without saving....)
<ProductGroups>
<ProductGroup>
<Name>ABC</Name>
<Id>123</Id>
</ProductGroup>
<ProductGroup>
<Name >xyz</Name>
<Id>456</Id>
</ProductGroup>
<ProductGroup>
<Name>PQR</Name>
<Id>789</Id>
</ProductGroup>
.
.
</ProductGroups>
I want to transform like this
<PRODUCTGROUPS>
<Name ID="123"> ABC</NAME>
<Name ID="456"> XYZ</NAME>
<Name ID="789">PQR</NAME>
.
.
</PRODUCTGROUPS>
I'm using C# with .NET.
From memory, may contain some errors:
var doc = XDocument.Load(...);
var groups = doc.Descendants(ProductGroup);
var newDoc = new XElement("ProductGroups",
groups.Select(pg => new XElement("Name",
new XAttribute("Id", pg.Element("Id").Value),
pg.Element("Name").Value) ));
newDoc.Save(...);
You could try it with linq2xml : (EDIT : roughly the same aproach as Henk Holterman)
XDocument sourceDocument = XDocument.Load("D:\\XmlFile.xml");
XDocument targetDocument = new XDocument();
var productGroupsElement = new XElement("ProductGroups");
sourceDocument.Descendants("ProductGroup").ToList().ForEach(productGroup =>
{
if (productGroup.Element("Name") != null && productGroup.Element("Id") != null)
{
var nameElement = new XElement("Name", productGroup.Element("Name").Value);
nameElement.Add(new XAttribute("Id", productGroup.Element("Id")));
productGroupsElement.Add(nameElement);
}
});
targetDocument.Add(productGroupsElement);
var resultXml = targetDocument.ToString(SaveOptions.None);

Linq to XML simple get attribute from node statement

Here's the code snippet:
XDocument themes = XDocument.Load(HttpContext.Current.Server.MapPath("~/Models/Themes.xml"));
string result = "";
var childType = from t in themes.Descendants()
where t.Attribute("name").Value.Equals(theme)
select new { value = t.Attribute("type").Value };
foreach (var t in childType) {
result += t.value;
}
return result;
and here's the XML:
<?xml version="1.0" encoding="utf-8" ?>
<themes>
<theme name="Agile">
<root type="Project">
<node type="Iteration" >
<node type="Story">
<node type="Task"/>
</node>
</node>
</root>
</theme>
<theme name="Release" >
<root type="Project">
<node type="Release">
<node type="Task" />
<node type="Defect" />
</node>
</root>
</theme>
</themes>
What am I doing wrong? I keep getting an "object not set to an instance of an object" exception.
What I'm trying to return is the type of the selected node based on the type of a parent node, i.e., if the theme is "Agile" and the parent node is "Project" then the return value should be "Iteration". That's the final outcome but I never got that far because I got stuck with what you see above.
I think you want something closer to this:
XDocument themes = XDocument.Load(HttpContext.Current.Server.MapPath("~/Models/Themes.xml"));
string result = "";
var childType = from t in themes.Descendants("theme")
where t.Attribute("name").Value.Equals(theme)
select new { value = t.Descendants().Attribute("type").Value };
foreach (var t in childType) {
result += t.value;
}
return result;
EDIT:
Based on your additional info, perhaps this is even closer:
XDocument themes = XDocument.Load(HttpContext.Current.Server.MapPath("~/Models/Themes.xml"));
string result = "";
var childType = from t in themes.Descendants("theme")
where t.Attribute("name").Value.Equals(theme)
where t.Element("node").Attribute("type").Value == parent
select new { value = t.Descendants().Attribute("type").Value };
foreach (var t in childType) {
result += t.value;
}
return result;
EDIT 2: This is working for me:
XDocument themes = XDocument.Load(HttpContext.Current.Server.MapPath("~/Models/Themes.xml"));
string result = "";
var theme = "Agile";
var parent = "Project";
var childType = from t in themes.Descendants("theme")
where t.Attribute("name").Value.Equals(theme)
where t.Element("root").Attribute("type").Value == parent
from children in t.Element("root").Descendants()
select new { value = children.Attribute("type").Value };
foreach (var t in childType) {
result += t.value;
}
return result;
EDIT 3: Here's the complete working program, just throw it in a class in a console application.
static void Main(string[] args)
{
var xml = "<themes><theme name='Agile'><root type='Project'><node type='Iteration' ><node type='Story'><node type='Task'/></node></node></root></theme><theme name='Release' ><root type='Project'><node type='Release'><node type='Task' /><node type='Defect' /></node></root></theme></themes>";
XDocument themes = XDocument.Parse(xml);
string result = "";
var theme = "Agile";
var parent = "Project";
var childType = from t in themes.Descendants("theme")
where t.Attribute("name").Value.Equals(theme)
where t.Element("root").Attribute("type").Value == parent
from children in t.Element("root").Descendants()
select new { value = children.Attribute("type").Value };
foreach (var t in childType)
{
Console.WriteLine(t.value);
}
Console.Read();
}
Ok here's what I did in the end, it's not very pretty but it works.
Its based on the answer by Pramodh with a few tweeks.
string result = "";
var childType = themes.Descendants("theme")
.Where(x => x.Attribute("name").Value == theme)
.Where(x => x.Descendants("node").First().Attribute("type").Value == parentType)
.Select(x => x.Descendants("node").First().Descendants().First().Attribute("type").Value);
foreach (var t in childType) {
result += t;
}
return result;
now if I pass in theme "Agile" and parent "Iteration" it returns story.
Like I said not very pretty but it works.
Thank you to everyone who posted answers.
There are two errors in your query.
First one is:
from t in themes.Descendants()
This will give you all themes elements.
If you want the theme elements, you must specify it, by filtering like:
from t in themes.Descendants("theme")
The second error will consequently be
select new { value = t.Attribute("type").Value }
because t will be a theme element, with no attribute "type".
I can't help on this one because it is not clear what the final result must be.
Edit: According to the added informations, this should do the trick:
var childType =
from t in doc.Descendants("theme")
where t.Attribute("name").Value.Equals(theme)
select t.Element("root")
.Element("node")
.Attribute("type").Value;
However, this is meant to retrieve multiple types from nodes with the same theme name.
If the type will always be the same, you should consider calling .SingleOrDefault() .
I'm not cleared about your question. Here is my solution( as i understood)
var childType = themes.Descendants("theme")
.Where(X => X.Attribute("name").Value == "Agile")
.Where(X => X.Descendants("root").Attributes("type").First().Value == "Project")
.Select(X => X.Descendants("node").Attributes("type").First().Value);

Categories