Get data from XML document using c# - c#

I would like to get datas from a specific XML format. The XML document looks like that:
<MyXML>
<Sources>
<S1>www.example1.org</S1>
<S2>www.example2.org</S2>
</Sources>
<Books>
<Book id="1">
<Datas>
<Data name="Book_1_Name" tag="1111" />
<Data name="Book_2_Name" tag="2222" />
</Datas>
</Book >
<Book id="2">
<Datas>
<Data name="Book_1_Name" tag="3333" />
<Data name="Book_2_Name" tag="4444" />
</Datas>
</Book >
</Books>
My question is:
How can I get www.example1.org if I know S1?
How can I search "Book_1_name" from Book id=1?
I am using C# with XDocument like this:
XDocument.Load(_XML_path);
var node = _configXml.XPathSelectElement("MyXML/Books/Datas");

You can use XPath, About XPath see this : http://www.xfront.com/xpath/
var _configXml = XDocument.Load(_XML_path);
//how to get S1 element.
var s1 = _configXml.XPathSelectElement("MyXML/Sources/S1");
//how search
var search = _configXml.XPathSelectElements("//Book[#id='1']//Data[#name='Book_1_Name']");
Console.WriteLine(s1.Value);
Console.WriteLine(search.First().Attribute("tag").Value);

You should map the XML to a C# object. Then, you can use the following to get what you want:
XmlSerializer serializer = new XmlSerializer(typeof(MyXML));
var xml = (MyXML)serializer.Deserialize(new StringReader(XDocument.Load(_XML_path)));
var s1 = xml.Sources.S1;

If you want to stick with a XDocument, you can do the following
var books = doc.Element("MyXML").Element("Books");
var bookById = books.Elements("Book")
.Where(b => b.Attribute("id").Value == "1")
.Select(b => b.Element("Datas"));
In the first line, you are selecting the Books node (please note, in a real-world scenario, I'd add some checks here). In the following line you are first getting all Book sub-elements (books.Elements("Book")), checking them for the ID you are searching for (Where(b => b.Attribute("id").Value == "1")) and then select the data node from the respective book. You could re-write this to query syntax
var bookById = from book in books.Elements("Book")
let id = book.Attribute("id")
let datas = book.Element("Datas")
where id != null
&& datas != null
&& id.Value == "1"
select datas;
which is a bit longer, but easier to read.

Related

how to take a specific child node value in xml using C#

I have an xml schema like below
<library>
<book>
<id>1</id>
<name>abc</name>
<read>
<data>yes</data>
<num>20</num>
</read>
</book>
<book>
<id>20</id>
<name>xyz</name>
<read>
<data>yes</data>
<num>32</num>
</read>
</book>
</library>
Now if the id is 20 i need to take the value of tag <num> under <read>
I done the code as below
var xmlStr = File.ReadAllText("e_test.xml");
var str = XElement.Parse(xmlStr);
var result = str.Elements("book").Where(x => x.Element("id").Value.Equals("20")).ToList();
this give the whole <book> tag with id 20. From this how can I extract only the value of tag <num>.
ie is i need to get the value 32 in to a variable
Before you try to extract the num value, you need to fix your Where clause - at the moment you're comparing a string with an integer. The simplest fix - if you know that your XML will always have an id element which has a textual value which is an integer - is to cast the element to int.
Next, I'd use SingleOrDefault to make sure there's at most one such element, assuming that's what your XML document should have.
Then you just need to use Element twice to navigate down via read and then num, and cast the result to int again:
// Or use XDocument doc = ...; XElement book = doc.Root.Elements("book")...
XElement root = XElement.Load("e_test.xml")
XElement book = root.Elements("book")
.Where(x => (int) x.Element("id") == 20)
.SingleOrDefault();
if (book == null)
{
// No book with that ID
}
int num = (int) book.Element("read").Element("num");
If you're not dead set on using Linq, how about this XPath? XPath is probably more widely understood, and is really simple. The XPath to find your node would be:
/library/book[id=20]/read/num
Which you could use in C# thus:
var doc = new XmlDocument();
doc.LoadXml(myString);
var id = 20;
var myPath = "/library/book[id=" + id + "]/read/num";
var myNode = doc.SelectSingleNode(myPath);
Then you can do whatever you like with myNode to get its value etc..
Helpful reference:
http://www.w3schools.com/xsl/xpath_syntax.asp

LINQ Selecting particular element

Basically, I am having trouble trying to understand where I am going wrong in here..
Basically, I have the following XML:
<Directory>
<CustDirectory name="Name1">
<Person name="Foo" />
<Person name="Goo" />
<Person name="Gentu" />
</CustDirectory>
<CustDirectory name="Name2">
<Person name="F22" />
<Person name="Gentu" />
</CustDirectory>
</Directory>
Using forms, I am updating a list of contacts and I want to write the list to XML depending on which category (stored as a string).
What I have decided to use is therefore LINQ to do it, but, I can't seem to figure out there .Where and have read through questions on stackoverflow and can't seem to figure it out.
Here is my attempt:
var con = e.Element("Directory").Element("CustDirectory").Descendants("Person").Where(p => p.Attribute("name").Value.ToStr == "Name2");
This does not work and throws up a null exception... When I take off the .Where clause, the data contained in the descendent shows correctly.
Could anyone please tell me where I am going wrong in terms of the LINQ query so I can select all the descendants of a particular root?
If I understood correctly your question you are trying to extract all the Person elements that belongs to a given CustDirectory. In this case you should use something more like:
var con = e.Element("Directory").Descendants("CustDirectory").Where(p => p.Attribute("name").Value == "Name2").Elements("Person");
Everything looks right except for the ToStr part.
To select only the Person elements under the CustDirectory with the name Name2 you will need to put your Where on that, like this:
var con = e.Element("Directory").Elements("CustDirectory")
.First(cd => cd.Attribute("name").Value == "Name2").Descendants("Person");
Note that I changed Element("CustDirectory") to Elements("CustDirectory").
You don't need Element("Directory") at the beginning, because e it self is already referenced <Directory> (different case if e is an XDocument instead of XElement as you said in comment). This example able to return <Person> nodes give sample XML posted in question :
var e = XElement.Parse("...");
var con = e.Elements("CustDirectory")
.Where(p => p.Attribute("name").Value == "Name2")
.Elements("Person");
void Main()
{
var xml = #"<Directory>
<CustDirectory name=""Name1"">
<Person name=""Foo""/>
<Person name=""Goo""/>
<Person name=""Gentu""/>
</CustDirectory>
<CustDirectory name=""Name2"">
<Person name=""F22""/>
<Person name=""Gentu""/>
</CustDirectory>
</Directory>";
var xmlDoc = XDocument.Parse(xml);
var con = xmlDoc.Element("Directory")
.Elements("CustDirectory")
.Where(p => p.Attribute("name").Value == "Name2")
.Descendants("Person")
// added bonus to get a specific node
.Where(p => p.Attribute("name").Value == "F22");
Console.WriteLine(con);
}

Linq to XML - Find an element

I am sure that this is basic and probably was asked before, but I am only starting using Linq to XML.
I have a simple XML that i need to read and write to.
<Documents>
...
<Document>
<GUID>09a1f55f-c248-44cd-9460-c0aab7c017c9-0</GUID>
<ArchiveTime>2012-05-15T14:27:58.5270023+02:00</ArchiveTime>
<ArchiveTimeUtc>2012-05-15T12:27:58.5270023Z</ArchiveTimeUtc>
<IndexDatas>
<IndexData>
<Name>Name1</Name>
<Value>Some value</Value>
<DataType>1</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
<IndexData>
<Name>Name2</Name>
<Value>Some value</Value>
<DataType>3</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
...
</IndexDatas>
</Document>
...
</Documents>
I have a "Documents" node that contains bunch of "Document" nodes.
I have GUID of the document and a "IndexData" name.
I need to find the document by GUID and check if it has "IndexData" with some name.
If it does not have it i need to add it.
Any help would be apreciated, as i have problem with reading and searching trough elements.
Currently I am trying to use (in C#):
IEnumerable<XElement> xmlDocuments = from c in XElement
.Load(filePath)
.Elements("Documents")
select c;
// fetch document
XElement documentElementToEdit = (from c in xmlDocuments where
(string)c.Element("GUID").Value == GUID select c).Single();
EDIT
xmlDocuments.Element("Documents").Elements("Document")
This returns no result, even tho xmlDocuments.Element("Documents") does. It looks like i cant get Document nodes from Documents node.
You can find those docs (docs without related name in index data) with below code, after that you could add your elements to the end of IndexData elements.
var relatedDocs = doc.Elements("Document")
.Where(x=>x.Element("GUID").Value == givenValue)
.Where(x=>!x.Element("IndexDatas")
.Elements("IndexData")
.Any(x=>x.Element("Name") == someValue);
This should work:
var x = XDocument.Load(filePath);
// guid in your sample xml is not a valid guid, so I changed it to a random valid one
var requiredGuid = new Guid("E61D174C-9048-438D-A532-17311F57ED9B");
var requiredName = "Name1";
var doc = x.Root
.Elements("Document")
.Where(d => (Guid)d.Element("GUID") == requiredGuid)
.FirstOrDefault();
if(doc != null)
{
var data = doc.Element("IndexDatas")
.Elements("IndexData")
.Where(d => (string)d.Element("Name") == requiredName)
.FirstOrDefault();
if(data != null)
{
// index data found
}
else
{
// index data not found
}
}
else
{
// document not found
}

Linq to XML : Increase performance

I want to read a xml file using Linq. This xml file is composed of 1 header and N Sub-Elements like this :
<rootNode>
<header>
<company name="Dexter Corp." />
</header>
<elements>
<element>one</element>
<element>eleven</element>
<element>three</element>
<element>four</element>
<element>five</element>
<element>three</element>
<element>two</element>
<element>two</element>
</elements>
</rootNode>
I want to retrieve elements which are a value (example : two). And only if I retrieve elements, I get the header elements.
Today, I do like this :
string xmlFilePath = #"C:\numbers.xml";
XDocument doc = XDocument.Load(xmlFilePath);
var header = doc.Descendants().Elements("header");
var elements = doc.Descendants()
.Elements("elements")
.Elements("element")
.Where(el => el.Value == "two");
// I get this values even if there is no 'elements'
string companyName = header.Descendants("company").Attributes("name").Single().Value;
string serialNumber = header.Descendants("serial").Single().Value;
return elements.Select(el => new {Company = companyName, Serial = serialNumber, Value = el.Value});
Is there a better way to parse the file ? (and increase performance ?)
If performance is important to you, you shouldn't use doc.Descendants() to look for an element at some known location. It always scans the whole document, which is slow if the document is big.
Instead, do something like
doc.Root.Element("header")
or
doc.Root.Element("elements")
.Elements("element")
.Where(el => el.Value == "two")
That should be much faster.

Update XML file with Linq

I'm having trouble trying to update my xml file with a new value. I have a class Person, which only contains 2 strings, name and description. I populate this list and write it as an XML file. Then I populate a new list, which contains many of the same names, but some of them contains descriptions that the other list did not contain. How can I check if the name in the current XML file contains a value other than "no description", which is the default for "nothing"?
Part of the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Names>
<Person ID="2">
<Name>Aaron</Name>
<Description>No description</Description>
</Person>
<Person ID="2">
<Name>Abdi</Name>
<Description>No description</Description>
</Person>
</Names>
And this is the method for writing the list to the xml file:
public static void SaveAllNames(List<Person> names)
{
XDocument data = XDocument.Load(#"xml\boys\Names.xml");
foreach (Person person in names)
{
XElement newPerson = new XElement("Person",
new XElement("Name", person.Name),
new XElement("Description", person.Description)
);
newPerson.SetAttributeValue("ID", GetNextAvailableID());
data.Element("Names").Add(newPerson);
}
data.Save(#"xml\boys\Names.xml");
}
In the foreach loop how do I check if the person's name is already there, and then check if the description is something other than "no description", and if it is, update it with the new information?
I'm not sure I understand properly what you want, but I'm assuming you want to update the description only when the name is already there and the description is currently No description (which you should probably change to an empty string, BTW).
You could put all the Persons into a Dictionary based by name:
var doc = …;
var persons = doc.Root.Elements()
.ToDictionary(x => (string)x.Element("Name"), x => x);
and then query it:
if (persons.ContainsKey(name))
{
var description = persons[name].Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
That is, if you care about performance. If you don't, you don't need the dictionary:
var person = doc.Root.Elements("Person")
.SingleOrDefault(x => (string)x.Element("Name") == name);
if (person != null)
{
var description = person.Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
You can use the Nodes-Method on XElement and check manually.
But i will advise you to use the XPathEvaluate-Extension Method
For XPath expression take a look at:
How to check if an element exists in the xml using xpath?
I think you could create a peoplelist which only contains people not in the xml.
like ↓
var containlist = (from p in data.Descendants("Name") select p.Value).ToList();
var result = (from p in peoplelist where !containlist.Contains(p.Name) select p).ToList();
so that , you would no need to change anything with your exist method ...
just call it after..
SaveAllNames(result);

Categories

Resources