Join two children - c#

I have this XML File:
<library>
<book ISBN="BSWE153" authors = "AC532" >
<title>Leraing XML</title>
<year>1995</year>
<publisher>W3C</publisher>
</book>
<author id="AC532">
<firstName>Hamdy</firstName>
<middleName/>
<lastName>Taha</lastName>
<nationality>Egypatian</nationality>
</author>
</library>
How to print the information of a book (with its authors information) given by its ISBN?

You can do that using XDocument, for example. You will need to
Parse your string (or load the file)
locate the book
read author Id (or ids?)
locate the author
nicely format it all together - with appropriate checks.
that is just a sketch:
const string xml = #"<?xml version=""1.0""?>
<library>
<book ISBN=""BSWE153"" authors = ""AC532"" >
<title>Leraing XML</title>
<year>1995</year>
<publisher>W3C</publisher>
</book>
<author id=""AC532"">
<firstName>Hamdy</firstName>
<middleName/>
<lastName>Taha</lastName>
<nationality>Egypatian</nationality>
</author>
</library>
";
var doc = XDocument.Parse(xml);
var book = doc.Root.Elements("book")
.FirstOrDefault(b => (string)b.Attribute("ISBN") == "BSWE153" );
var authorId = book.Attribute("authors").Value.ToString();
var author = doc.Root.Elements("author")
.FirstOrDefault(b => (string)b.Attribute("id") == authorId );
Console.WriteLine("{0} by {1} {2}",
book.Element("title").Value,
author.Element("firstName").Value,
author.Element("lastName").Value);

Related

Can I get a distinct list of XML TagName using C#?

I have a very long xml file and I need to identify what are the distinct TagName in that xml file. I wonder if I can get it in my C# application with XmlDocument library.
In this example xml, I want to find all the TagName: bookstore, book genre, title, first name
<bookstore>
<book genre="novel">
<title>The Autobiography of Benjamin Franklin</title>
</book>
<book genre="novel">
<title>The Confidence Man</title>
<first-name>Herman</first-name>
</book>
</bookstore>
Parse it as an XDocument and you could do this:
var names = doc.Descendants().Select(e => e.Name.LocalName).Distinct();
This will give you the results (in some order):
bookstore
book
title
first-name
Otherwise if you must use an XmlDocument, you could do this:
var names = doc.DocumentElement
.SelectNodes("//*").Cast<XmlNode>()
.Select(e => e.LocalName)
.Distinct();
You can use HashSet to get distinct names. Moreover, it is very fast.
var doc = XDocument.Load("test.xml");
var set = new HashSet<string>();
foreach (var node in doc.Descendants())
{
set.Add(node.Name.LocalName);
foreach (var attr in node.Attributes())
set.Add(attr.Name.LocalName);
}
foreach (var name in set)
Console.WriteLine(name);

c# select attribute or element from XML

<bookstore>
<book>
<bookID>100</bookID>
<name> The cat in the hat </name>
</book>
<book>
<bookID>90</bookID>
<name> another book </name>
</book>
<book>
<bookID>103</bookID>
<name> a new book </name>
</book>
</bookstore>
I'm trying to sellect the bookID value from a XML document.The method I currently use is Element(bookID).Value But sometimes the bookID comes as an attribute of book like this: <book bookID=100> Is there a way of doing that in c#, like a xPath expression maybe? Many thanks!
In XPath, you can use union (|) operator to combine query that return different part of the XML. For example :
//book/bookID/text() | //book/#bookID
The above XPath returns text content of bookID element and bookID attribute in document order. See the demo which using XmlDocument.SelectNodes() to execute the XPath.
Demo Codes :
var xml = #"<bookstore>
<book>
<bookID>100</bookID>
<name> The cat in the hat </name>
</book>
<book bookID='90'>
<name> a new book </name>
</book>
<book>
<bookID>103</bookID>
<name> another book </name>
</book>
</bookstore>";
var doc = new XmlDocument();
doc.LoadXml(xml);
var result = doc.SelectNodes("//book/bookID/text() | //book/#bookID");
foreach (XmlNode r in result)
{
Console.WriteLine(r.Value);
}
output :
100
90
103
Does this need to be an XPath expression or can you use Linq with the XDocument system.
For example.
var xDocument = XDocument.Parse(#"<bookstore>
<book>
<bookID>100</bookID>
<name> The cat in the hat </name>
</book>
<book bookID=""90"">
<name> another book </name>
</book>
<book>
<bookID>103</bookID>
<name> a new book </name>
</book>
</bookstore>");
foreach (var xBook in xDocument.Descendants("book"))
{
var bookIdNode = xBook.Elements("bookID").FirstOrDefault();
int bookId = 0;
///there is a book id as an element
if (bookIdNode != null)
{
//invalid book id.. should be an int
if (!int.TryParse(bookIdNode.Value, out bookId))
continue;
}
else
{
var bookIdAttr = xBook.Attributes("bookID").FirstOrDefault();
if (bookIdAttr == null || !int.TryParse(bookIdAttr.Value, out bookId))
continue;
}
if (bookId == 0)
continue;
//else we got our book id
}
This code is quite simple, just enumerates over the descendants with the element name book. It first checks if there is an element named bookID (case sensitive). If there is it attempts to parse the book id out as an int using the method int.TryParse().
If there are no bookID elements it next checks if there are any attributes with the name bookID and grabs the first instance (or null) using FirstOrDefault() extension method. If there is an instance of the bookID attribute it also try to parse the int using the int.TryParse() method.
By the end of the small snippet we have then check if the bookId is 0 if it is zero we can assume something went wrong. However this shouldnt happen as the logic should keep enumerating and forget about Elements without a bookID element or bookID attribute.

Lambda linq repeated information

I have the next code that i developed myself. It works, but if in the XML has 3 records, it will show the three records, but all of them will have the same information that the last one has.
//Load file and puts it on a IEnumerable interface
IEnumerable<MObject> MObjects = XDocument.Load(v_urlDataConnection)
.Element("MTestData").Descendants("book")
.Select(x => new MObject
{
Title = x.Element("title").Value,
Artist = x.Element("artist").Value,
Company = x.Element("company").Value,
Country = x.Element("country").Value,
Year = x.Element("year").Value,
Price = float.Parse(x.Element("price").Value)
}).ToList();
The xml is:
<MTestData>
<book>
<title>Scenes</title>
<artist>Dream Theater</artist>
<company>Vertigo</company>
<country>USA</country>
<year>1999</year>
<price>19.99</price>
</book>
<book>
<title>Falling</title>
<artist>Dream Theater</artist>
<company>3</company>
<country>3</country>
<year>3</year>
<price>3</price>
</book>
<book>
<title>Images and Words</title>
<artist>Dream Theater</artist>
<company>Elektra</company>
<country>USA</country>
<year>1991</year>
<price>15</price>
</book>
</MTestData>

Getting the previous node of a searched keyword in xml

I have an xml document like the following:
<bookstore>
<book>
<title>The Autobiography of Benjamin Franklin</title>
<author>
<first-name>Benjamin</first-name>
<last-name>Franklin</last-name>
</author>
<price>8.99</price>
</book>
<book>
<title>Zen and the Art of Motorcycle Maintenance</title>
<author>
<first-name>Robert</first-name>
<last-name>Pirsig</last-name>
</author>
<price>5.99</price>
</book>
<book>
<title>Other Cities</title>
<author>
<first-name>Benjamin</first-name>
<last-name>Rosenbaum</last-name>
</author>
<price>9.99</price>
</book>
</bookstore>
Of course, the bookstore has more than one book, so I I want to search for an author and then get returned an XElement for the book node that contains the searched author name.
var document = XDocument.Parse(xml);
var bookElements = document.Descendants("book")
.Where(arg => arg.Element("author").Element("first-name").Value == "Benjamin")
.ToList();
or
var bookElements = document.Descendants("first-name")
.Where(arg => arg.Value == "Benjamin")
.Select(arg => arg.Parent.Parent)
.ToList();
[Edit] As you keep editing the question, I will edit the answer :).
To find the first book that meets the criteria:
var foundBookElement = document.Descendants("book")
.Where(arg => arg.Element("author").Element("first-name").Value == "Benjamin")
.FirstOrDefault();
foundBookElement will be null if none of the books match the criteria.

C# How to extract complete xml node set

<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
Is their any way using XPath to select the complete first node set, for example from
<book category="COOKING">
to
</book>,
so that, that chunk of xml can be stored for later use.
Bob.
Let's say this XML is stored in an XmlDocument called doc.
XmlElement docRoot = doc.DocumentElement;
XmlNode cookingNode = docRoot.SelectSingleNode("./book[#category='COOKING']");
I tested this and added this line to verify:
Console.WriteLine(cookingNode.OuterXml);
Here was the output:
<book category="COOKING"><title lang="en">Everyday Italian</title><author>Giada
De Laurentiis</author><year>2005</year><price>30.00</price></book>
This query will select that node. Are you trying to get a set of nodes or just the single one? You might have to put the bookstore node back yourself if you only want th subset of nodes.
/bookstore/book[#category='COOKING']
as XmlDocument ...
var x = new XmlDocument();
x.Load("XmlFile1.xml");
var ns = x.SelectSingleNode("/bookstore/book[#category='COOKING']");
var res = ns.OuterXml;
as XDocument ...
var x = XDocument.Load("XmlFile1.xml");
var root = new XElement("bookstore",
from book in x.Element("bookstore").Elements("book")
where book.Attribute("category").Value == "COOKING"
select book
);
if you just want the book node you can do this instead of the root version above
var book = x.Element("bookstore")
.Elements("book")
.Where(n => n.Attribute("category").Value == "COOKING")
.First();
suppose I want to extract only the data wherethe xml file is as follows ..
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author auth="up">Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
the final result on the list view should look like this
lang auth
en up
I have coded as follows ..
XmlNodeList elemList = doc.GetElementsByTagName("book");
for (int j = 0; j < elemList.Count; j++)
{
if (elemList[j].Attributes["category"].Value == "COOKING")
{
XmlNodeList elemList1 = doc.GetElementsByTagName("author");
for (int i = 0; i < elemList1.Count; i++)
{
string attrVal = elemList1[i].Attributes["lang"].Value;
string attrVal1 = elemList1[i].Attributes["auth"].Value;
ListViewItem lvi = new ListViewItem();
lvi.SubItems.Add(attrVal1);
lvi.SubItems.Add(attrVal1);
}
listView1.Items.Add(lvi);
}
}
}
Adding to Matthew's response:
XmlDocument xDoc = new XmlDocument();
// (Put code to populate xDoc here)
XmlNodeList xNode = xDoc.SelectNodes(#"/bookstore/book[#category='COOKING']");
xNode now equals Book of type COOKING.

Categories

Resources