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

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();

Related

Parsing SOAP response in C#

I am new C#. I make a SOAP request and in the SOAP response, I need to access repeating nodes 'ABC'. This is how my SOAP Response looks like:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header>
<work:WorkContext xmlns:work="http://example.com/soap/workarea/">sdhjasdajsdhj=</work:WorkContext>
</env:Header>
<env:Body>
<ReadABCResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.xyz.com/abc/a6/AB/XYZ/V1">
<ABC xmlns="http://xmlns.xyz.example/abc/a6/AB/XYZ/V1">
<asd xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true"/>
<xyz xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true"/>
</ABC>
<ABC xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1">
<asd xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true"/>
<xyz xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true"/>
</ABC>
</ReadABCResponse>
</env:Body>
</env:Envelope>
My code is as below:
XmlDocument responseDoc = new XmlDocument();
responseDoc.LoadXml(responseString); //responseString is set to above SOAP response.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(responseDoc.NameTable);
nsmgr.AddNamespace("env", "http://schemas.xmlsoap.org/soap/envelope/");
nsmgr.AddNamespace("work", "http://example.com/soap/workarea/");
nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
nsmgr.AddNamespace("", "http://xmlns.example.com/abc/a6/AB/XYZ/V1");
XmlNodeList lst = responseDoc.SelectNodes("/env:Envelope/env:Body/ReadABCResponse/ABC", nsmgr);
Console.WriteLine("Count " + lst.Count);
// and then iterate over the repeating ABC nodes to do some work.
However value of Count is always printed as 0. I have tried different combinations of the xpath path in "SelectNodes" method including "//ABC" - which I thought should give me all the repeating 'ABC' nodes but it does not.
What is wrong with my code. please can someone highlight and help me!
I have looked around on this site but cant figure out what is wrong with this code.
The following shows how to use XDocument to read data from the XML.
Test.xml:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header>
<work:WorkContext xmlns:work="http://example.com/soap/workarea/">sdhjasdajsdhj=</work:WorkContext>
</env:Header>
<env:Body>
<ReadABCResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.xyz.com/abc/a6/AB/XYZ/V1">
<ABC xmlns="http://xmlns.xyz.example/abc/a6/AB/XYZ/V1">
<asd xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true">asd data 1</asd>
<xyz xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true">xyz data 1</xyz>
</ABC>
<ABC xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1">
<asd xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true">asd data 2</asd>
<xyz xmlns="http://xmlns.example.com/abc/a6/AB/XYZ/V1" xsi:nil="true">xyz data 2</xyz>
</ABC>
</ReadABCResponse>
</env:Body>
</env:Envelope>
Add the following using statements:
using System.Xml;
using System.Xml.Linq;
using System.Diagnostics;
Create a class (name: ABC.cs)
public class ABC
{
public string Asd { get; set; }
public string Xyz { get; set; }
}
Option 1:
private void GetABC()
{
//ToDo: replace with your XML data
string xmlText = "your XML data...";
//parse XML
XDocument doc = XDocument.Parse(xmlText);
//create new instance
List<ABC> abcs = new List<ABC>();
foreach (XElement elem in doc.Descendants().Where(x => x.Name.LocalName == "ABC"))
{
//create new instance
ABC abc = new ABC();
foreach (XElement elemChild in elem.Descendants())
{
//Debug.WriteLine($"{elemChild.Name}: '{elemChild.Value?.ToString()}'");
if (elemChild.Name.LocalName == "asd")
abc.Asd = elemChild.Value?.ToString();
else if (elemChild.Name.LocalName == "xyz")
abc.Xyz = elemChild.Value?.ToString();
}
//add to List
abcs.Add(abc);
}
foreach (ABC abc in abcs)
{
Debug.WriteLine($"ABC: '{abc.Asd}' XYZ: '{abc.Xyz}'");
}
}
Option 2:
private void GetABC()
{
//ToDo: replace with your XML data
string xmlText = "your XML data...";
//parse XML
XDocument doc = XDocument.Parse(xmlText);
//get namespace
XNamespace nsABC = doc.Descendants().Where(x => x.Name.LocalName == "ABC").FirstOrDefault().GetDefaultNamespace();
List<ABC> abcs = doc.Descendants().Where(x => x.Name.LocalName == "ABC").Select(x2 => new ABC()
{
Asd = (string)x2.Element(nsABC + "asd"),
Xyz = (string)x2.Element(nsABC + "xyz")
}).ToList();
foreach (ABC abc in abcs)
{
Debug.WriteLine($"ABC: '{abc.Asd}' XYZ: '{abc.Xyz}'");
}
}
Resources:
XDocument
How to Read SOAP XML Response in VB.NET
You can also do this: copy the xml and paste it in Visual studio using Paste special -> Paste Xml as classes. Now you will have an Envelope class and you can deserialize the xml like in this example:https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer.deserialize?view=net-7.0.
Soap messages can be quite complex and it would be easier to work with objects which you can modify and eventually serialize back if needed.
Thanks for your response guys but I managed to solve it. In the namsspace manager, I made the following change
nsmgr.AddNamespace("x", "http://xmlns.example.com/abc/a6/AB/XYZ/V1");
and when listing the ABC nodes, I made the following change i.e. to prefix ABC with x:
XmlNodeList lst = responseDoc.SelectNodes("//x:ABC", nsmgr);
Rest of the code remains as it is and now I can loop through all the ABC nodes.

Fetch sibling nodes linq

I have an XML as below. I will have to fetch the title when the path is d:\mypath. I tried below one but it is not giving as expected. I would like implement it in LINQ to XML.
My code:
XDocument xdoc = XDocument.Load(file);
string mypath = #"D:\\Mypath";
var result = xdoc.Descendants("child")
.Where(i => (string)i.Element("content").Element("path") == mypath)
.Select(i => (string)i.Element("title")).FirstOrDefault();
For now I have finished my task using XPathSelectElement as below, but I am interested to in LINQ query :
string a = (string)xdoc.XPathSelectElement(
"//child/content[path='" + mypath + "']/../doc_attributes/title");
Sample XML:
<parent>
<doc>
<order>testorder</order>
<preorder>yes</preorder>
</doc>
<childs>
<child>
<doc_attributes>
<id>090015b3804fb931</id>
<title>CTA</title>
</doc_attributes>
<content>
<path>D:\\Mypath</path>
</content>
</child>
</childs>
</parent>
You're close, you're just forgetting to check the Value property
.Where(i => i.Element("content").Element("path").Value == mypath)

How to access element name using attribute value from Xml

I have an XML file with code:
<?xml version="1.0" encoding="utf-8"?>
<car_ads>
<car_make make="suzuki" adj_kw="null">
<model data_type="string"adj_kw="null" class="کار_ماڈل ">
<model_instance>ALTO</model_instance>
<model_instance>KHYBER</model_instance>
</model>
<year data_type="integer" adj_kw="yes" class="ایر ">
<adj_kw>ماڈل </adj_kw>
<adj_kw>ء</adj_kw>
</year>
<price data_type="string" adj_kw="yes" class=" قیمت " >
<adj_kw>قیمت </adj_kw>
<adj_kw>ڈیمانڈ </adj_kw>
</price>
</car_make>
<car_make make="سوزوکی" adj_kw="null">
<model data_type="string" adj_kw="null" class="کار_ماڈل ">
<model_instance>alto</model_instance>
<model_instance>آلٹو</model_instance>
</model>
<year data_type="integer" adj_kw="yes" class="ایر ">
<adj_kw>ماڈل </adj_kw>
<adj_kw>ء</adj_kw>
<adj_kw>ایئرآفمینوفیکچرنگ </adj_kw>
</year>
<price data_type="string" adj_kw="yes" class=" قیمت " >
<adj_kw>قیمت </adj_kw>
<adj_kw>ڈیمانڈ </adj_kw>
</price>
</car_make>
</car_ads>
I am parsing this using XmlDocument in c#
string xmlText = File.ReadAllText(#"G:\\car_xml_final.xml");
var doc = new XmlDocument();
doc.LoadXml(xmlText);
If I know attribute value (e.g.in my example attribute class =" ایر") I want to get its corresponding element name (i.e element= "year").
Thanks #Charles Mager for pointing out the difference between XmlDocument and XDocument. If you use XDocument, you can use either LINQ:
var element = doc.Root.Descendants().FirstOrDefault(e => e.Attribute("class") == " ایر");
var elementName = element.Name;
or XPath:
var element = doc.XPathSelectElement("//[#class=' ایر']");
var elementName = element.Name;
to get your desired result.
If you stick to XmlDocument, there's the SelectSingleNode method:
var element = doc.DocumentElement.SelectSingleNode("descendant::[class=""' ایر'""]");
As mentioned in the other answer, you can use SelectSingleNode() or SelectNodes() to get sepcific element(s) from XmlDocument passing an XPath expression as parameter, for example :
var result = doc.SelectNodes("//*[#class='ایر ']");
foreach (XmlNode node in result)
{
//print element name
Console.WriteLine(node.Name);
}
brief explanation about XPath being used :
//* : select all elements regardless of the name (*), anywhere in the XML document (//)...
[#class='some_class_here'] : ...having class attribute value equals certain class name

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);

Filter XDocument more efficiently

I would like to filter with high performance XML elements from an XML document.
Take for instance this XML file with contacts:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="asistentes.xslt"?>
<contactlist evento="Cena Navidad 2010" empresa="company">
<contact type="1" id="1">
<name>Name1</name>
<email>xxxx#zzzz.es</email>
<confirmado>SI</confirmado>
</contact>
<contact type="1" id="2">
<name>Name2</name>
<email>xxxxxxxxx#zzzze.es</email>
<confirmado>Sin confirmar</confirmado>
</contact>
</contaclist>
My current code to filter from this XML document:
using System;
using System.Xml.Linq;
class Test
{
static void Main()
{
string xml = #" the xml above";
XDocument doc = XDocument.Parse(xml);
foreach (XElement element in doc.Descendants("contact")) {
Console.WriteLine(element);
var id = element.Attribute("id").Value;
var valor = element.Descendants("confirmado").ToList()[0].Value;
var email = element.Descendants("email").ToList()[0].Value;
var name = element.Descendants("name").ToList()[0].Value;
if (valor.ToString() == "SI") { }
}
}
}
What would be the best way to optimize this code to filter on <confirmado> element content?
var doc = XDocument.Parse(xml);
var query = from contact in doc.Root.Elements("contact")
let confirmado = (string)contact.Element("confirmado")
where confirmado == "SI"
select new
{
Id = (int)contact.Attribute("id"),
Name = (string)contact.Element("name"),
Email = (string)contact.Element("email"),
Valor = confirmado
};
foreach (var contact in query)
{
...
}
Points of interest:
doc.Root.Elements("contact") selects only the <contact> elements in the document root, instead of searching the whole document for <contact> elements.
The XElement.Element method returns the first child element with the given name. No need to convert the child elements to a list and take the first element.
The XElement and XAttribute classes provide a wide selection of convenient conversion operators.
You could use LINQ:
foreach (XElement element in doc.Descendants("contact").Where(c => c.Element("confirmado").Value == "SI"))

Categories