I am trying to Join two XML based on the same elements, but my code doesn't return anything. The var result is empty. Can someone please help to sovle the problem? Many thanks in advance!
file one:
<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>
file two
<bookstore>
<book>
<bookID>100</bookID>
<content> story </content>
</book>
<book>
<bookID>90</bookID>
<content> fiction </content>
</book>
<book>
<bookID>103</bookID>
<content> bio </content>
</book>
</bookstore>
the result I'm looking for is something like:
<result>
<bookInfo>
<bookID>103</bookID>
<name> a new book </name>
<content> bio </content>
<bookInfo>
</result>
The (wrong) code I am currently using is:
var reslut =
from a in fileone.Descendants("bookstore")
join b in filetwo.Descendants("bookstore")
on (string)fileone.Descendants("bookID").First() equals (string)filetwo.Descendants(""bookID"").First()
select new XElement("bookInfo", a, b);
You want to join <book> elements on <bookID> child value, and then return<bookInfo> elements containing bookID, name, and content elements :
var bookInfos =
from a in fileone.Descendants("book")
join b in filetwo.Descendants("book")
on (string)a.Element("bookID") equals (string)b.Element("bookID")
select new XElement("bookInfo",
a.Element("bookID"),
a.Element("name"),
b.Element("content")
);
var result = new XElement("result", bookInfos);
Console.WriteLine(result.ToString());
Dotnetfiddle Demo
output :
<result>
<bookInfo>
<bookID>100</bookID>
<name> The cat in the hat </name>
<content> story </content>
</bookInfo>
<bookInfo>
<bookID>90</bookID>
<name> another book </name>
<content> fiction </content>
</bookInfo>
<bookInfo>
<bookID>103</bookID>
<name> a new book </name>
<content> bio </content>
</bookInfo>
</result>
Related
I am trying to get rid of the parent node of a collection in xml (using linq-to-xml):
For example, the input xml:
<envelop>
<books>
<book>
<title>ABC</title>
<publishedDate>2012-12-12</publishedDate>
<authors>
<author>John Smith</author>
<author>Bob Doe</author>
</authors>
<book>
<book>
<title>XYZ</title>
<publishedDate>2013-03-06</publishedDate>
<authors>
<author>Henry Blah</author>
<author>Bob Doe</author>
</authors>
<book>
</books>
</envelop>
I would like the result to be:
<envelop>
<book>
<title>ABC</title>
<publishedDate>2012-12-12</publishedDate>
<author>John Smith</author>
<author>Bob Doe</author>
<book>
<book>
<title>XYZ</title>
<publishedDate>2013-03-06</publishedDate>
<author>Henry Blah</author>
<author>Bob Doe</author>
<book>
</envelop>
The nodes: <books> and <authors> are gone - only their children remain.
I saw the following question but with XSLT (Remove parent nodes from xml with xslt). Would like to stay away from XSLT at the moment.
var document = XDocument.Load(validXmlFilePath);
while (document.Descendants("authors").Any())
{
var x = document.Descendants("authors").First();
x.AddAfterSelf(x.Nodes());
x.Remove();
}
var result = document.Descendants("book");
Do you fill your object in code and than serialize it? If so, you can use [XmlElement("Book")] attribute above your list property in your class:
[XmlElement("Book")]
public List<Book> Books {get; set;}
In VB the code would be
Dim result As XElement = <envelop></envelop>
result.Add(xe...<book>)
For Each el As XElement In result...<author>.ToList
el.Parent.Parent.Add(New XElement(el))
Next
result...<authors>.Remove()
with xe defined as
Dim xe As XElement
'to load from a file
' xe = XElement.Load("Your Path Here")
' for testing
xe = <envelop>
<books>
<book>
<title>ABC</title>
<publishedDate>2012-12-12</publishedDate>
<authors>
<author>John Smith</author>
<author>Bob Doe</author>
</authors>
</book>
<book>
<title>XYZ</title>
<publishedDate>2013-03-06</publishedDate>
<authors>
<author>Henry Blah</author>
<author>Bob Doe</author>
</authors>
</book>
</books>
</envelop>
result looks like
<envelop>
<book>
<title>ABC</title>
<publishedDate>2012-12-12</publishedDate>
<author>John Smith</author>
<author>Bob Doe</author>
</book>
<book>
<title>XYZ</title>
<publishedDate>2013-03-06</publishedDate>
<author>Henry Blah</author>
<author>Bob Doe</author>
</book>
</envelop>
I have xml document similar to xml in page http://www.w3schools.com/xml/xml_xpath.asp
<?xml version="1.0" encoding="UTF-8"?>
<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>
the problem is how to return this document with elements corresponding to a certain value? how to XPath or XQuery command should be written ?
for example search for title contains 'Learning' then the return xml doc should be like:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="web">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
how to get this result?
another question how to search with ignoring characters case, so 'learNING' should return the same result?
Using XQuery you can do as follow :
<bookstore>
{
for $d in //book[contains(lower-case(title),'learning')]
return $d
}
</bookstore>
Xpathtester Demo
Notice that there will be only one <bookstore> that wraps all matched <book> elements returned, and notice the use of lower-case() function to 'ignore' character case of the book title in the matching process.
Use 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);
List<XElement> book = doc.Descendants("book").Where(x => x.Element("title").Value.Contains("Learn")).ToList();
XDocument filteredDoc = XDocument.Parse("<?xml version=\"1.0\" encoding=\"UTF-8\"?><bookstore></bookstore>");
XElement root = (XElement)filteredDoc.FirstNode;
root.Add(book);
}
}
}
for $d in doc('books')//book[title[contains(text(),'Beginning')]]
return <bookstore> {$d} </bookstore>
However this solution does not handle ignoring character case.
<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.
Hi all
I have a xml file:
<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>30</price>
</book>
<book category="CHILDREN">
<title lang="en">abc</title>
<author>bcd</author>
<year>2006</year>
<price>29</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>
How to I can get price list when category = "CHILDREN"? I using C#.
example: Output is : price of Harry Potter is 30 and price of abc is 29
Thank,
You can use LINQ to XML. Following code will return collection of anonymous type instances with all data from your XML:
var xDoc = XDocument.Load("Input.txt");
var books = xDoc.Root
.Elements("book")
.Where(b => (string)b.Attribute("category") == "CHILDREN")
.Select(b => new
{
Title = (string)b.Element("title"),
Author = (string)b.Element("author"),
Year = (int)b.Element("year"),
Price = (decimal)b.Element("price")
});
You can call .First() method on books to get only one/first found item.
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.