get elemets from elemets in complex XML using LINQ - c#

I have a complex xml and I need to get all the elements
<sdnEntry> containing the value "Individual" in the tag <sdnType>
this is my XML:
string list = #"<?xml version='1.0' standalone='yes'?>
<sdnList xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://tempuri.org/sdnList.xsd'>
<sdnEntry>
<uid>36</uid>
<lastName>AEROCARIBBEAN</lastName>
<sdnType>Entity</sdnType>
<programList>
<program>CUBA</program>
</programList>
</sdnEntry>
<sdnEntry>
<uid>173</uid>
<lastName>ANGLO-CARIBBEAN</lastName>
<sdnType>Entity</sdnType>
<programList>
<program>CUBA</program>
</programList>
</sdnEntry>
<sdnEntry>
<uid>2681</uid>>
<title>NAME1 SURNNAME1</title>
<sdnType>Individual</sdnType>
<programList>
<program>SDGT</program>
</programList>
</sdnEntry>
<sdnEntry>
<uid>2682</uid>
<title>NAME2 SURNNAME2</title>
<sdnType>Individual</sdnType>
<programList>
<program>SDGT</program>
</programList>
<idList>
<id>
<uid>1002</uid>
<idType>Passport</idType>
<idNumber>304555</idNumber>
<idCountry>Egypt</idCountry>
</id>
</idList>
</sdnEntry>
</sdnList>";
I have tried this so far, but it is not working:
listXML.LoadXml(list);
XDocument sndList = XDocument.Parse(listXML.OuterXml);
var nls = XNamespace.Get("http://tempuri.org/sdnList.xsd");
//TEST 1
//--------------------------------------------------------------
var individ = sndList.Elements(nls + "sdnEntry")
.SelectMany(r => r.Descendants("sdnType").Where(e => (string)e.Element("sdnType").Value == "Individual"));
//TEST 2
//--------------------------------------------------------------
IEnumerable<XElement> individuals = from element in sndList.Root.Elements(nls + "sdnEntry")
where (string)element.Element("sdnType") == "Individual"
select element;
what am I doing wrong?
Thanks for helping.

Use this:
var individualNodes = sndList.Root.Elements(nls + "sdnEntry")
.Where(e => e.Element(nls + "sdnType").Value == "Individual");

Related

c# Merging two XMLs giving error

I am trying to merge two XMLs with same structure but different data into one.
I am getting this error: A node of type Document cannot be added to content.
Below is my code
var productElements =
testGroupProvider.GetTestGroup().ProductTests.Select(
productTest => new XElement(xNamespace + "Product",
new XElement(xNamespace + "ExternalId", productTest.ProductNameKey),
new XElement(xNamespace + "Name", testGroupProvider.GetProductName(productTest)),
new XElement(xNamespace + "ImageUrl", ChoiceBaseHostName + GetProductImageUrl(productTest, TargetDatabase))));
var root = new XDocument(
new XElement(xNamespace + "Feed",
new XAttribute("xmlns", xNamespace),
new XAttribute("name", BVFeedsName),
new XAttribute("incremental", "true"),
new XAttribute("extractDate", DateTime.Now.ToString("o")),
new XElement(xNamespace + "Categories",
new XElement(xNamespace + "Category",
new XElement(xNamespace + "ExternalId", testGroupProvider.GetProductGroup().Id),
new XElement(xNamespace + "Name", testGroupProvider.GetProductGroup().Name),
testGroupProvider.GetTestGroup().Name),
new XElement(xNamespace + "Products", productElements)));
var filePath = #"D:\testXML\test.xml";
XElement xml = XElement.Load(filePath);
xml.Add(root);
xml.Save(filePath);
Can anyone tell me what i am doing wrong.
This is the XML structure in test.xml
<?xml version="1.0" encoding="utf-8"?>
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
The second XML has the same structure with different products
<?xml version="1.0" encoding="utf-8"?>
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
I want to combine them like below
<?xml version="1.0" encoding="utf-8"?>
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
A xml document must have only one root.
Working with the documents you attached, you can replace the xml.Add(root); with the following (i.e. it will add each node under one root to the other xml root)
foreach (var child in root.Root.Elements())
{
xml.Element(child.Name.ToString()).Add(child.Nodes());
}
Edit - A further generalization
You can generalize the above code using a Merge extension of 2 XElements so that it reads as follows
foreach (var child in root.Elements())
{
xml.Element(child.Name.ToString()).Merge(child, xNamespace + "ExternalId");
}
Having defined the extension
public static void Merge(this XElement root1, XElement root2, XName element_id)
{
root1.Add(root2.Elements().Except(root1.Elements(), new MyComparer(element_id)));
}
with a xml comparer
public class MyComparer : IEqualityComparer<XElement>
{
private XName _element_id;
public MyComparer(XName element_id)
{
_element_id = element_id;
}
public bool Equals(XElement x, XElement y)
{
return x.Element(_element_id).Value.Equals(y.Element(_element_id).Value);
}
public int GetHashCode(XElement el)
{
return el.Element(_element_id).Value.GetHashCode();
}
}
Select correct nodes to add and correct nodes to be added.
var filePath = #"D:\testXML\test.xml";
XElement xml = XElement.Load(filePath);
var xmlCategories = xml.Descendants("Categories").First();
var rootCategories = root.Descendants("Category");
xmlCategories.Add(rootCategories);
var xmlProducts = xml.Descendants("Products").First();
var rootProducts = root.Descendants("Product");
xmlProducts.Add(rootProducts);
xml.Save(filePath);
Be crystal clear what you are doing.
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
const string FILENAME1 = #"c:\temp\test1.xml";
const string FILENAME2 = #"c:\temp\test2.xml";
static void Main(string[] args)
{
XDocument doc1 = XDocument.Load(FILENAME1);
XDocument doc2 = XDocument.Load(FILENAME2);
XElement category1 = doc1.Descendants().Where(x => x.Name.LocalName == "Categories").FirstOrDefault();
XElement category2 = doc2.Descendants().Where(x => x.Name.LocalName == "Categories").FirstOrDefault();
category1.Add(category2.Descendants());
XElement product1 = doc1.Descendants().Where(x => x.Name.LocalName == "Products").FirstOrDefault();
XElement product2 = doc2.Descendants().Where(x => x.Name.LocalName == "Products").FirstOrDefault();
product1.Add(product2.Descendants());
}
}
}
Try this, sorry about the VB
'second is The second XML has the same structure with different products
Dim combined As XElement = New XElement(test) 'create copy of test.xml
combined.<Categories>.LastOrDefault.Add(second.<Categories>.Elements)
combined.<Products>.LastOrDefault.Add(second.<Products>.Elements)
or
'if test can be used to combine then
test.<Categories>.LastOrDefault.Add(second.<Categories>.Elements)
test.<Products>.LastOrDefault.Add(second.<Products>.Elements)
The result is
<Feed name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00" xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
The test data I used is
Dim test As XElement =
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
Dim second As XElement =
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
The XElements can be loaded like this
test = XElement.Load("PATH")
second = XElement.Load("second PATH")
and saved like this
test.Save("PATH")
second.Save("second PATH")
combined.Save("combined PATH")

Get Resutls from XML with more than one line

I want get Order Lines for each Order and bring them to an EMail Body as Table like
Value 1 | Value 2 | Value 3
12345 ABC X1_
XML Code:
<?xml version="1.0" encoding="utf-8"?><UniversalInterchange xmlns= "http://www.designworker/namespace" version="1.1">
<Header> </Header>
<Body>
<UniversalShipment xmlns="http://www.designworker/namespace" version="1.1">
<Shipment>
<DataContext>
<DataTargetCollection>
<DataTarget>
<Type>SMTPTYPE</Type>
</DataTarget>
</DataTargetCollection>
</DataContext>
<Order>
<OrderNumber>99348234234</OrderNumber>
<OrderLineCollection>
<OrderLine>
<LineNumber>1</LineNumber>
<OrderedQty>455.000</OrderedQty>
<OrderedQtyUnit>
<Code>MORE</Code>
</OrderedQtyUnit>
<Product>
<Code>999LAM01</Code>
<Description>New Design Work</Description>
</Product>
<QuantityMet>1.000</QuantityMet>
<ShortfallQuantity>0</ShortfallQuantity>
</OrderLine>
<OrderLine>
<LineNumber>2</LineNumber>
<OrderedQty>655.000</OrderedQty>
<OrderedQtyUnit>
<Code>SOME</Code>
</OrderedQtyUnit>
<Product>
<Code>999LAM02</Code>
<Description>OLD Design Work </Description>
</Product>
<QuantityMet>3.000</QuantityMet>
<ShortfallQuantity>45</ShortfallQuantity>
</OrderLine>
</OrderLineCollection>
</Order>
</Shipment>
</UniversalShipment>
</Body>
</UniversalInterchange>
I have tried to solve this with this code:
var xDoc = XDocument.Parse(xmlValue);
XNamespace nsp = ns;
try
{
var value = xDoc
.Element(nsp + "UniversalInterchange")
.Element(nsp + "Body")
.Element(nsp + "UniversalShipment")
.Element(nsp + "Order")
.Element(nsp + "OrderLine")
.Element(nsp + "LineNumber")
.Value;
return value;
But when I have more than one Line in the Order it won't work.
How can I solve this on a better way ?
Looks like the navigation path is missing a few .Element()-calls.
var orderLines = xDoc
.Element(nsp + "UniversalInterchange")
.Element(nsp + "Body")
.Element(nsp + "UniversalShipment")
.Element(nsp + "Shipment")
.Element(nsp + "Order")
.Element(nsp + "OrderLineCollection")
.Elements(nsp + "OrderLine");
// 1, 2
var lineNumbers = orderLines.Select(x => x.Element(nsp + "LineNumber").Value);
Whenever an element isn't found, null is returned from .Element(). You should also use .Elements() instead of .Element() if you expect multiple entries.

How to parse specific fields in XML to c# class?

I have an XML looking like this :
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ocrsdk.com/schema/resultDescription-1.0.xsd http://ocrsdk.com/schema/resultDescription-1.0.xsd" xmlns="http://ocrsdk.com/schema/resultDescription-1.0.xsd">
<page index="0">
<text id="print" left="160" top="349" right="339" bottom="384">
**<value>Vertraqsnummer:</value>**
<line left="167" top="366" right="326" bottom="384">
<char left="167" top="366" right="180" bottom="382">V</char>
<char left="287" top="370" right="302" bottom="382">
<charRecVariants>
<variant charConfidence="22">m</variant>
<variant charConfidence="-1">rn</variant>
</charRecVariants>m</char>
<char left="304" top="370" right="314" bottom="382">e</char>
<char left="316" top="370" right="322" bottom="382">r</char>
<char left="324" top="370" right="326" bottom="382" suspicious="true">:</char>
</line>
</text>
<text id="handprint" left="387" top="1035" right="635" bottom="1089">
**<value>309.05</value>**
<line left="398" top="1045" right="633" bottom="1089">
<char left="398" top="1052" right="426" bottom="1088">3</char>
<char left="423" top="1061" right="455" bottom="1089" suspicious="true">0</char>
<char left="546" top="1045" right="633" bottom="1089" suspicious="true">5</char>
</line>
</text>
<checkmark id="checked" left="883" top="427" right="928" bottom="469">
**<value>checked</value>**
</checkmark>
<checkmark id="not checked" left="884" top="511" right="928" bottom="554">
**<value>unchecked</value>**
</checkmark>
<barcode id="leftBarcode" left="46" top="1048" right="128" bottom="1350">
<value encoding="Base64">QkYxMDExNQ==</value>
</barcode>
I want to be able to parse only the fields where XXX is written, take the value inside and place it under the field of my c# class.
For example, for this XML, I want to take these values:
**<value>Vertraqsnummer:</value>**
**<value>309.05</value>**
**<value>checked</value>**
using class "A" for example :
class A
{
public string s1;
public string s2;
public string s3;
}
my result should be :
s1 = Vertraqsnummer
s2 = 309.05
s3 = checked
I looked at some questions here but the only thing I noticed is that I can use XsdObjectGen or XSD.exe. The problem is that they take the whole XML and not only the parts I need.
any help would be very appriciated!
XmlNamespaceManager nsMgr = new XmlNamespaceManager(new NameTable());
nsMgr.AddNamespace("ns", "http://ocrsdk.com/schema/resultDescription-1.0.xsd");
var result = XDocument.Load(filename)
.XPathSelectElements("//ns:text/ns:value|//ns:checkmark[#id='checked']/ns:value", nsMgr)
.Select(x => x.Value)
.ToList();
or without XPATH
XNamespace ns = "http://ocrsdk.com/schema/resultDescription-1.0.xsd";
var xDoc = XDocument.Load(filename);
var result = xDoc.Descendants(ns + "text")
.Union(xDoc.Descendants(ns + "checkmark").Where(c => (string)c.Attribute("id") == "checked"))
.Select(x => x.Element(ns + "value").Value)
.ToList();

LINQ to XML: How to get all elements by value

I'm trying to get all elements with a given value, "John", from an xml document.
Is this possible with LINQ to XML?
What I want to achieve is to replace all "John" values with "Wayne". I know this can easily be done with xslt, but I need to do this by code.
My XML:
<Root>
<Parents>
<Parent>
<Name>John</Name>
<Age>18</Age>
</Parent>
<Parent>
<Name>John</Name>
<Age>25</Age>
</Parent>
<Parent>
<Name>Peter</Name>
<Age>31</Age>
</Parent>
</Parents>
</Root>
I have tried this:
XmlDocument doc = new XmlDocument();
doc.Load(#"C:/Temp/test.xml");
var elements = doc.Elements().Where(w => w.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
You may use System.Xml.Linq.XDocument. It's more easy to work with.
XDocument doc = XDocument.Load(your file path);
var elements = doc.Descendants("Name").Where(i => i.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
doc.Save(your save file path);
Here is the output:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Parents>
<Parent>
<Name>Wayne</Name>
<Age>18</Age>
</Parent>
<Parent>
<Name>Wayne</Name>
<Age>25</Age>
</Parent>
<Parent>
<Name>Peter</Name>
<Age>31</Age>
</Parent>
</Parents>
</Root>
Here is an approach that will get all elements with the value John, regardless of what element (although only at the same level; you'd have to modify it to look at different levels too; you could use the Descendants approach described previously):
XDocument doc = XDocument.Load(#"C:\temp\test.xml");
var ns = doc.Root.GetDefaultNamespace();
var elements = doc.Element(ns + "Root").Element(ns + "Parents").Elements(ns + "Parent").Elements().Where(w => w.Value == "John");
foreach (var element in elements)
{
element.Value = "Wayne";
}
var stream = new FileStream(#"C:\temp\test.xml", FileMode.Create);
doc.Save(stream);

Needing C# LINQ Help reading XML

I've got xml that looks like what I have below. I can read the title but am having trouble getting to the url of media:content. Any suggestions? My non-working c# is below along with the xml.
XNamespace xmlns = "http://www.w3.org/2005/Atom";
var names =
(from data in XDocument.Load("http://channel9.msdn.com/Events/Build/2012/rss").Descendants("item")
let xElement = data.Element("title")
let xElementUrls = data.Element("media")
where xElement != null
select new
{
Title = xElement.Value,
Urls = data.Elements(xmlns + "media:group")
//MediaGroup = data.Element("media:group")
}).ToList();
and the XML:
<?xml-stylesheet type="text/xsl" media="screen" href="/styles/xslt/rss.xslt"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
xmlns:c9="http://channel9.msdn.com">
<channel>
<item>
<title>Building Windows 8 LOB Apps (Repeat)</title>
<media:group>
<media:content url="http://video.ch9.ms/sessions/build/2012/2-104R.mp4"
expression="full" duration="0" fileSize="1" type="video/mp4" medium="video"/>
<media:content url="http://video.ch9.ms/sessions/build/2012/2-104R.wmv"
expression="full" duration="0" fileSize="1" type="video/x-ms-wmv" medium="video"/>
</media:group>
</item>
<item>
<item>
...
</item>
Added this based on L.B.'s suggestion but I can't figure out how to get url's out (it is now returning a list of URL's per item as expected.
var items = xDoc.Descendants("item")
.Where(g => g.Element(media + "group") != null)
.Select(g => new {
Title = g.Element("title").Value,
Url = g.Element(media + "group")
.Element(media + "content")
.Attribute("url").Value
})
.ToList();
var xDoc = XDocument.Load("http://channel9.msdn.com/Events/Build/2012/rss");
XNamespace media = "http://search.yahoo.com/mrss/";
var items = xDoc.Descendants("item")
.Where(g => g.Element(media + "group") != null)
.Select(g => new {
Title = g.Element("title").Value,
Url = g.Element(media + "group")
.Element(media + "content")
.Attribute("url").Value
})
.ToList();
media is a namespace alias, not an element.
You need to get the group element within the http://search.yahoo.com/mrss/ namespace:
XNamespace m = "http://search.yahoo.com/mrss/";
let xElementUrls = data.Element(m + "group")
You have the wrong namespace...
XNamespace xmlns = "http://www.w3.org/2005/Atom";
should be
XNamespace xmlns = "http://search.yahoo.com/mrss/";
And you are combining the namespace and element name incorrectly
...
Urls = data.Elements(xmlns + "group")
....

Categories