How do I differentiate between different descendents with the same name? - c#

I've got some XML I'm trying to import with c#, which looks something like this:
<root>
<run>
<name = "bob"/>
<date = "1958"/>
</run>
<run>
<name = "alice"/>
<date = "1969"/>
</run>
</root>
I load my xml using
XElement xDoc=XElement.Load(filename);
What I want to do is have a class for "run", under which I can store names and dates:
public class RunDetails
{
public RunDetails(XElement xDoc, XNamespace xmlns)
{
var query = from c in xDoc.Descendants(xmlns + "run").Descendants(xmlns + "name") select c;
int i=0;
foreach (XElement a in query)
{
this.name= new NameStr(a, xmlns); // a class for names
Name.Add(this.name); //Name is a List<NameStr>
i++;
}
// Here, i=2, but what I want is a new instance of the RunDetails class for each <run>
}
}
How can I set up my code to create a new instance of the RunDetails class for every < run>, and to only select the < name> and < date> inside a given < run>?

You can just LINQ to XML to create an IEnumerable from your XML.
IEnumerable<RunDetail> runDetails = from run in xdocument.Descendants("run")
select new RunDetail
{
Name = run.Element("name").Value,
Date = int.Parse(run.Element("date").Value)
};
This, of course, suggests there's a class named RunDetail with public Name and Date (int for the year) properties. You can iterate over the enumerable as it is, or if you need more explicit access to the individual members, you can use .ToList() or .ToArray() to convert the query.

You need to have some parent element in your xml cause your one is not valid. If you have following xml
<root>
<run>
<name = "bob"/>
<date = "1958"/>
</run>
<run>
<name = "alice"/>
<date = "1969"/>
</run>
</root>
you can load it to XDocument and iterate through children of the root element. For each run child element you can create RunDetails.

Related

C# Find Attribute in XML with another Attribute with Descendants

First I want to find and select the PID"5678" from the <Tool>. With help of this PID, i want to find and select the ID"5678" from the <Parent>. The PID and the ID are the same value, but I have to find it from the <Tool> first.
At the moment I have following Code, to select the first PID. How can I "copy" this value and search with them the Attribute "ID"?
List<string> urls = xmldoc2.Descendants("PID").Select(x => x.Attribute("5678").Value).ToList();
<Tools>
<Tools>
<Tool>
<ID>1234</ID>
<PID>5678</PID>
<Name>Test</Name>
</Tool>
</Tools>
<Type>
<Parent>
<ID>5678</ID>
<PID>9999</PID>
<Name>Test2</Name>
</Parent>
</Type>
</Tools>
Notice that your Xml has multiple Root nodes - which does not work well.
So wrap it into single parent node (i.e. "Root" in below example)
Something of this sort should help you.
string xmlData = #"... Your Xml here....";
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlData);
var pidNodes = xmlDoc.SelectNodes("//Root/Tools/Tools/Tool/PID");
foreach(XmlNode node in pidNodes)
{
var typeNodeForPid = xmlDoc.SelectSingleNode(string.Format("//Root/Type/Parent[ID = '{0}']", node.InnerText));
}

C# Add nodes in xml

I am trying to add my code for already existing code. I have below two xmls:
Out.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>123</ID>
<Name>AAA</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
In.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>456</ID>
<Name>BBB</Name>
</Student>
<Student SequenceNumber="2">
<ID>123</ID>
<Name>AAA</Name>
</Student>
<Student SequenceNumber="3">
<ID>789</ID>
<Name>CCC</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
Now I need to check Out.xml and In.xml and my final Out.xml must be like below. The rule here is check StudentID in Out and In xmls. If Out xml doesnot have it and In xml has it add it to Out.xml at end of already existing elements.
Out.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>123</ID>
<Name>AAA</Name>
</Student>
<Student SequenceNumber="2">
<ID>456</ID>
<Name>BBB</Name>
</Student>
<Student SequenceNumber="3">
<ID>789</ID>
<Name>CCC</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
Already existing code is as below
string inFileName = #"C:\In.xml";
string inXml = System.IO.File.ReadAllText(inFileName);
var xmlReaderSource = XmlReader.Create(new StringReader(inXml));
var mgr = new XmlNamespaceManager(xmlReaderSource.NameTable);
mgr.AddNamespace("m", "http://www.mismo.org/residential/2009/schemas");
XDocument sourceXmlDoc = XDocument.Load(xmlReaderSource);
string outFileName = #"C:\Out.xml";
string outXml = System.IO.File.ReadAllText(outFileName);
XmlDocument targetXmlDoc = new XmlDocument();
targetXmlDoc.LoadXml(outXml);
I cannot change above code now I need add my logic.
I added like below
string xpath = #"/m:School/m:Classes/m:Class/m:Students";
XmlNodeList outStudentNodes = targetXmlDoc.SelectNodes(xpath + "/m:Student", namespaceManager);
if(outStudentNodes== null || outStudentNodes.Count <= 0)
{
return;
}
XElement root = sourceXmlDoc.Root;
IEnumerable<XElement> inStudentsColl = from item in root.Elements("Classes").Descendants("Class")
.Descendants("Students").Descendants("Student")
select item;
Now I have XmlNodeList and IEnumerble, trying to see whether I can use LINQ statement and make code simple for my comparison.
Node: I am not asking how to add nodes/elements using C#. I am looking for how to compare two xmls and then add nodes/elements into the one which is missing those nodes/elements. My issue here is one xml is read like XDocument and other using XmlDocument.
UPDATE
Thank you very much #TheAnatheme. I really appreciate it.
I followed what TheAnatheme suggested me and it worked. I marked TheAnatheme's answer as real solution. Please see below what I did in foreach block so that if anyone wants to use they can refer to this post.
string xpath = #"/m:School/m:Classes/m:Class/m:Students
XmlNode studentsNode = targetXmlDoc.SelectSingleNode(xpath, namespaceManager);
foreach (var element in elementsToAdd)
{
//Add Microsoft.CSharp.dll (if needed ) to your project for below statement to work
dynamic studentElement = element as dynamic;
if (studentElement != null)
{
XmlElement studentXmlElement = targetXmlDoc.CreateElement("Student");
XmlElement studentIDXmlElement = targetXmlDoc.CreateElement("ID");
studentIDXmlElement.InnerText = studentElement.ID;
XmlElement studentNameXmlElement = targetXmlDoc.CreateElement("Name");
studentNameXmlElement .InnerText = studentElement.Name;
studentXmlElement.AppendChild(studentIDXmlElement);
studentXmlElement.AppendChild(studentNameXmlElement);
studentsNode.AppendChild(childElement);
}
}
This projects both sets into an anonymous object List, makes comparisons, and gives you a set of anonymous objects that don't yet exist by which you can add to the out XML.
public static List<object> GetInStudents(XDocument sourceXmlDoc)
{
IEnumerable<XElement> inStudentsElements =
sourceXmlDoc.Root.Elements("Classes").Descendants("Class")
.Descendants("Students").Descendants("Student");
return inStudentsElements.Select(i =>
new { Id = i.Elements().First().Value,
Name = i.Elements().Last().Value }).Cast<object>().ToList();
}
public static List<object> GetOutStudents(XmlDocument targetXmlDoc)
{
XmlNodeList outStudentsElements = targetXmlDoc.GetElementsByTagName("Students")[0].ChildNodes;
var outStudentsList = new List<object>();
for (int i = 0; i < outStudentsElements.Count; i++)
{
outStudentsList.Add(new { Id = outStudentsElements[i].ChildNodes[0].InnerText,
Name = outStudentsElements[i].ChildNodes[1].InnerText });
}
return outStudentsList;
}
And you compare them as such:
var inStudents = GetInStudents(sourceXmlDoc);
var outStudents = GetOutStudents(targetXmlDoc);
if (inStudents.SequenceEqual(outStudents))
{
return;
}
else
{
var elementsToAdd = inStudents.Except(outStudents);
foreach (var element in elementsToAdd)
{
// create xmlNode with element properties, add element to xml
}
}

XML - where node has same name but different values

I am trying to read an xml file (and later import the data in to a sql data base) which contains employees names address' etc.
The issue I am having is that in the xml the information for the address for the employee the node names are all the same.
<Employee>
<EmployeeDetails>
<Name>
<Ttl>Mr</Ttl>
<Fore>Baxter</Fore>
<Fore>Loki</Fore>
<Sur>Kelly</Sur>
</Name>
<Address>
<Line>Woof Road</Line>
<Line>Woof Lane</Line>
<Line>Woofington</Line>
<Line>London</Line>
</Address>
<BirthDate>1985-09-08</BirthDate>
<Gender>M</Gender>
<PassportNumber>123756rt</PassportNumber>
</EmployeeDetails>
</Employee>
I all other items are fine to extract and I have tried to use Linq to iterate through each "Line" node but it always just gives be the first Line and not the others.
var xAddreesLines = xEmployeeDetails.Descendants("Address").Select(x => new
{
address = (string)x.Element("Line").Value
});
foreach (var item in xAddreesLines)
{
Console.WriteLine(item.address);
}
I need to able to when I'm importing to my sql db that address line is separate variable
eg
var addressline1 = first <line> node
var addressline2 = second <line> node etc etc.
Any advice would be most welcome.
This should give you the expected output:-
var xAddreesLines = xdoc.Descendants("Address")
.Elements("Line")
.Select(x => new { address = (string)x });
You need to simply fetch the Line elements present inside Address node and you can project them. Also note there is no need to call the Value property on node when you use explicit conversion.
You can do it like this:
using System.Xml;
.
.
.
XmlDocument doc = new XmlDocument();
doc.Load("source.xml");
// if you have the xml in a string use doc.LoadXml(stringvar)
XmlNamespaceManager nsmngr = new XmlNamespaceManager(doc.NameTable);
XmlNodeList results = doc.DocumentElement.SelectNodes("child::Employee", nsmngr);
foreach (XmlNode result in results)
{
XmlNode namenode = result.SelectSingleNode("Address");
XmlNodeList types = result.SelectNodes("line");
foreach (XmlNode type in types)
{
Console.WriteLine(type.InnerText);
}
XmlNode fmtaddress = result.SelectSingleNode("formatted_address");
}
Refer to this question for the original source.

Reading data from XML with attributes

I have an XML file which is like below:
<CPageDataXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<control id="busRowOAppr2EIDLookUpUserControl" controltype="business">
<field controlvaluetype="single" key="busRowOAppr2EIDLookUpUserControl_txtEID">
<valuefield value="709227">E8 - John Doe</valuefield>
</field>
<field controlvaluetype="hidden_single" key="busRowOAppr2EIDLookUpUserControl_txtEID_Email">
<valuefield value="_JohnDoe#Wonder.com">emailid</valuefield>
</field>
</control>
<control id="busDelegationFromDate123" controltype="business">
<field controlvaluetype="single" key="txtCalanderDateWithImage_UserControl">
<valuefield value="" />
</field>
</control>
</CPageDataXML>
I want to read the value of the valuefield where control id="busRowOAppr2EIDLookUpUserControl"
The C# code is:
This is the code for loading the XML:
XmlDocument xPagedata=new XmlDocument();
XmlNode xnodePagedata = null;
xPagedata.LoadXml(strPageData);
This is the code for SelectSingleNode:
string a = xnodePagedata.SelectSingleNode(//Control[#id='busRowOAppr2EIDLookUpUserControl']).Attributes["Value"].Value;
I have tried to use SelectSingleNode(string) but that is giving me a null reference exception. Kindly suggest how should I go about this one. I am an absolute beginer on XML.
One possible way using the same approach :
string a =
xnodePagedata.SelectSingleNode("//control[#id='busRowOAppr2EIDLookUpUserControl']/field/valuefield/#value")
.Value;
UPDATE :
In case there are multiple <valuefield> in one <control> and you want all values, use SelectNodes() for example :
var values =
xPagedata.SelectNodes("//control[#id='busRowOAppr2EIDLookUpUserControl']/field/valuefield/#value");
foreach (XmlNode value in values)
{
Console.WriteLine(value.Value);
}
You can use XDocument : use Descendants("control") to get all controls then filter them using the Where clause then use SelectMany to get a flattened collection of values of valuefield.
XDocument doc = XDocument.Load(filepath);
var result = doc.Descendants("control")
.Where(i => (string)i.Attribute("id") == "busRowOAppr2EIDLookUpUserControl")
.SelectMany(i => i.Descendants("valuefield")
.Select(j => j.Attribute("value")))
.ToList();
And this is the result:
result Count = 2
[0] {value="709227"}
[1] {value="_JohnDoe#Wonder.com"}

Override values in one Xml file from another

Looking for a way to merge to XML files where the modified attributes in the second file should override the values of the objects in the first file. Seems like this should be doable with linq to xml but having some trouble figuring out how to do it.
For example take the following two XML files:
File 1:
<root>
<foo name="1">
<val1>hello</val1>
<val2>world</val2>
</foo>
<foo name="2">
<val1>bye</val1>
</foo>
</root>
File 2:
<root>
<foo name="1">
<val2>friend</val2>
</foo>
</root>
The desired end result would be to merge File 2 in to File 1 and end up with
<root>
<foo name="1">
<val1>hello</val1>
<val2>friend</val2>
</foo>
<foo name="2">
<val1>bye</val1>
</foo>
</root>
Sub 'foo' elements should be uniquely identified by their 'name' value with any set values in File 2 overriding the values in File 1.
Any pointers in the right direction would be much appreciated, thanks!
You can just iterate and update values - don't know how generic you want this to be though...
class Program
{
const string file1 = #"<root><foo name=""1""><val1>hello</val1><val2>world</val2></foo><foo name=""2""><val1>bye</val1></foo></root>";
const string file2 = #"<root><foo name=""1""><val2>friend</val2></foo></root>";
static void Main(string[] args)
{
XDocument document1 = XDocument.Parse(file1);
XDocument document2 = XDocument.Parse(file2);
foreach (XElement foo in document2.Descendants("foo"))
{
foreach (XElement val in foo.Elements())
{
XElement elementToUpdate = (from fooElement in document1.Descendants("foo")
from valElement in fooElement.Elements()
where fooElement.Attribute("name").Value == foo.Attribute("name").Value &&
valElement.Name == val.Name
select valElement).FirstOrDefault();
if (elementToUpdate != null)
elementToUpdate.Value = val.Value;
}
}
Console.WriteLine(document1.ToString());
Console.ReadLine();
}
}
You can build new xml from these two:
XDocument xdoc1 = XDocument.Load("file1.xml");
XDocument xdoc2 = XDocument.Load("file2.xml");
XElement root =
new XElement("root",
from f in xdoc2.Descendants("foo").Concat(xdoc1.Descendants("foo"))
group f by (int)f.Attribute("name") into foos
select new XElement("foo",
new XAttribute("name", foos.Key),
foos.Elements().GroupBy(v => v.Name.LocalName)
.OrderBy(g => g.Key)
.Select(g => g.First())));
root.Save("file1.xml");
Thus foo elements from second file selected first, they will have priority over foo elements from first file (when we do grouping).

Categories

Resources