Deserialize XML using XmlSerializer - c#

I have the follwoing XML
<?xml version="1.0" ?>
<SERVICES.OUTPUTResponse>
<Results>
<Result>
<Dataset name="OutputData">
<Row>
<country>USA</country>
<pubyear>9986</pubyear>
<numart>123</numart>
<numcites>456</numcites>
</Row>
<Row>
<country>USA</country>
<pubyear>97</pubyear>
<numart>895</numart>
<numcites>231</numcites>
</Row>
</Dataset>
<Dataset name="Result 2">
<Row>
<Result_2>
true
</Result_2>
</Row>
</Dataset>
</Result>
</Results>
<_Probe></_Probe>
</SERVICES.OUTPUTResponse>
and i tried to deserialize by using XmlSerializer but it returns null.
The Property class which i used is
public class XMLDetails
{
public string country { get; set; }
public string pubyear { get; set; }
public string numart { get; set; }
public string numcites { get; set; }
}
Deserialize code is
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "SERVICES.OUTPUTResponse";
xRoot.IsNullable = true;
var serializer = new XmlSerializer(typeof(XMLDetails), xRoot);
var reader = new StringReader(remoteXml);
var objpublication = (XMLDetails)(serializer.Deserialize(reader));
Please help me to reslove it

If you want to use Linq To Xml
XDocument xDoc = XDocument.Parse(xml); //or XDocument.Load(filename);
var rows = xDoc.XPathSelectElement("//Dataset[#name='OutputData']")
.Descendants("Row")
.Select(r => new XMLDetails
{
country = r.Element("country").Value,
pubyear = r.Element("pubyear").Value,
numart = r.Element("numart").Value,
numcites = r.Element("numcites").Value,
})
.ToList();
PS: required namespaces System.Xml.Linq and System.Xml.XPath

First you need to use xsd.exe for generating .xsd (schema) file and .cs (class) file
XML Schema Definition Tool (Xsd.exe)
You can run "Visual Studio Command Prompt" and xsd.exe path definition is already defined it is ready to use.
Type the following command in the console
*I assume your xml is saved in "yourxmlfile.xml"
>xsd.exe yourxmlfile.xml
this command will generate "yourxmlfile.xsd" file
then execute the following command for generating .cs file
But before change the
<xs:element name="Results" minOccurs="0" maxOccurs="unbounded"> line with
<xs:element name="Results" minOccurs="0" maxOccurs="1"> in generated xsd file
(to change Results to property instead of array propery)
>xsd.exe yourxmlfile.xsd /c
this command will generate "yourxmlfile.cs"
now you can add this file to your project and you can deserialize xml file as below
var serializer = new XmlSerializer(typeof(SERVICESOUTPUTResponse));
SERVICESOUTPUTResponse instance = null;
using (var fileStream = File.OpenRead(#"c:\path_to\yourxmlfile.xml"))
{
instance = (SERVICESOUTPUTResponse)serializer.Deserialize(fileStream);
}

Related

How to merge one specific tag into one XML using c#?

I have two XMLs'
XML1:
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>3421</Value>
<Value>John</Value>
</Object>
</Sequence>
</Data>'
XML2:
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>1234</Value>
<Value>SAM</Value>
</Object>
</Sequence>
</Data>'
I want the output like below
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>3421</Value>
<Value>John</Value>
</Object>
<Object>
<Value>1234</Value>
<Value>SAM</Value>
</Object>
</Sequence>
</Data>'
I.e I want to merge Object tag from XML2 to XML1 using C# code.
Could someone please help me?
You can use XPath to select the nodes you need and then simply use .NET xml using System.Xml
For more information look at https://www.w3schools.com/xml/xpath_intro.asp
Load Xml Documents
I saved the two sample xml-files you provided to separate files and imported them like this
XmlDocument doc1 = new XmlDocument();
XmlDocument doc2 = new XmlDocument();
using (var sw = new StreamReader("xml1.xml"))
{
var text = sw.ReadToEnd();
doc1.LoadXml(text);
}
using (var sw = new StreamReader("xml2.xml"))
{
var text = sw.ReadToEnd();
doc2.LoadXml(text);
}
Select nodes with XPATH
We will take all elements that have the name 'object' and add them to child of the other xml's 'sequence'-element. Therefore we select the 'sequence'-element of one document and the 'object'elements of the other document.
var sequenceNodes = doc1.SelectSingleNode("/Data/Sequence");
var objectNodes = doc2.SelectNodes("/Data/Sequence/Object");
Concat the nodes into one document
Then we take each 'object'-element, import it into the other document-context and append it under the 'sequence'-node
foreach (XmlNode node in objectNodes)
{
XmlNode importedNode = doc1.ImportNode(node, true);
sequenceNodes.AppendChild(importedNode);
}
Output the file
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
doc1.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
File.AppendAllText("out.xml", stringWriter.GetStringBuilder().ToString());
}
The outputfile looks like this:
Please, take a look at this solution:
what-is-the-fastest-way-to-combine-two-xml-files-into-one
Using Union method seems to be what you are seeking.
Run an XSLT transformation:
<xsl:template name="merge">
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<xsl:copy-of select="document('a.xml')//Object"/>
<xsl:copy-of select="document('b.xml')//Object"/>
</Sequence>
</Data>
</xsl:template>
Try following linq which joins all filenames with same value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication166
{
class Program
{
const string FILENAME1 = #"c:\temp\test.xml";
const string FILENAME2 = #"c:\temp\test1.xml";
static void Main(string[] args)
{
XDocument doc1 = XDocument.Load(FILENAME1);
XNamespace ns1 = doc1.Root.GetDefaultNamespace();
XDocument doc2 = XDocument.Load(FILENAME2);
XNamespace ns2 = doc2.Root.GetDefaultNamespace();
var joins= from d1 in doc1.Descendants(ns1 + "Data")
join d2 in doc2.Descendants(ns2 + "Data")
on (string)d1.Element(ns1 + "Filename") equals (string)d2.Element(ns2 + "Filename")
select new { d1 = d1, d2 = d2};
foreach (var join in joins)
{
XElement d2Object = join.d2.Descendants("Object").FirstOrDefault();
join.d1.Descendants("Sequence").FirstOrDefault().Add(XElement.Parse(d2Object.ToString()));
}
}
}
}

Using Linq to XML to parse XML file with separate metadata element

I have an xml file with the following format that I'm trying to parse with C# Linq to XML. The problem is that it has this separate metadata element which is the only thing identifying the values below. Is there a good way to do this? I don't have any power to change the format of this file.
<?xml version="1.0" encoding="utf-8?>
<dataset xmlns="http://developer.cognos.com/schemas/xmldata/1/" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance">
<metadata>
<item name="Address Line 1" type="xs:string" length="512"/>
<item name="Address Line 2" type="xs:string" length="512"/>
<item name="Date Of Birth" type="xs:dateTime"/>
</metadata>
<data>
<row>
<value>123 Main St</value>
<value xs:nil="true" />
<value>1970-01-01T00:00:00</value>
</row>
<row>
<value>125 Main St</value>
<value>Apt 1</value>
<value>1980-01-01T00:00:00</value>
</row>
</data>
</dataset>
The actual file has about 30 item and corresponding value elements in each row and several hundred row elements following this format. I'm basically looking for the best way to match up the metadata to the values. If Linq to XML is not the best way to achieve this, I'm open to other suggestions that work with C# and .NET 4.5.
I tried just collecting the metadata items in a list and using indices to match them to the values, but it seems to build the list in an arbitrary order, so I'm not sure I can rely on that ordering to identify the values.
XDocument xdoc = XDocument.Load(#"Export.xml");
XNamespace xns = "http://developer.cognos.com/schemas/xmldata/1/";
var metadataQuery = from t in xdoc.Descendants(xns + "item") select t;
List<XElement> metadata = metadataQuery.ToList(); // This list appears to be ordered randomly
The order of elements is always the same, in the order in which they appear in the xml file.
var xdoc = XDocument.Load(#"Export.xml");
XNamespace xns = "http://developer.cognos.com/schemas/xmldata/1/";
var metadata = xdoc.Descendants(xns + "item").ToList();
var data = xdoc.Descendants(xns + "row").ToList();
foreach (var datum in data)
{
var values = datum.Elements(xns + "value").ToList();
for (int i = 0; i < values.Count; i++)
{
Console.WriteLine(metadata[i].Attribute("name").Value + ": " + values[i].Value);
}
Console.WriteLine();
}
Here is a simple method to parse file
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);
XElement dataset = (XElement)doc.FirstNode;
XNamespace ns = dataset.Name.Namespace;
var results = doc.Descendants(ns + "row").Select(x => new {
firstAddr = (string)x.Elements(ns + "value").FirstOrDefault(),
secondAddr = (string)x.Elements(ns + "value").Skip(1).FirstOrDefault(),
dob = (DateTime)x.Elements(ns + "value").Skip(2).FirstOrDefault()
}).ToList();
}
}
}

C# Serialize List to XML with Sequence attribute autogenerated?

I have to generate (serialize to) XML from object with array of (Order) elements.
Order class generated from XSD has sequence attribute:
[System.Xml.Serialization.XmlAttributeAttribute(DataType = "token")]
public string Sequence;
I am using .Net XMLSerializer, but it does not generate automagically for every Order element Sequence attribute.
Having:
Order[] Orders = new Order[2] {...}
I have to get:
<Order Sequence="1">..</Order>
<Order Sequence="2">..</Order>
And for just one, single element it should render:
<Order Sequence="1">..</Order>
Does anyone know how to make XMLSerialzier renders this attribute automagically? Or do I need to manually set Sequence for every Order element?
Cheers
AFAIK there is no way to achieve this with out-of-the-box methods. This leaves you with the following options:
IXmlSerializable
Implement IXmlSerializable on the object that contains the array of Orders. This way, you can either serialize the Orders manually or set the sequence number of the Order before serializing the object into the XmlWriter using the XmlSerializer.Serialize method:
public class OrdersContainer : IXmlSerializable
{
public Order[] Orders;
public void WriteXml(XmlWriter writer)
{
// Serialize other properties
writer.WriteStartElement("Orders");
var ser = new XmlSerializer(typeof(Order));
for(var i = 0; i < Orders.Length; i++)
{
Orders[i].Sequence = (i + 1).ToString();
ser.Serialize(writer, Orders[i]);
}
writer.WriteEndElement(); // Orders
}
// ...
}
This generated the following XML:
<?xml version="1.0" encoding="utf-16"?>
<OrdersContainer>
<Orders>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Sequence="1">
<Name>Order 1</Name>
</Order>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Sequence="2">
<Name>Order 2</Name>
</Order>
</Orders>
</OrdersContainer>
The XmlSerializer places some namespace declarations on the Order elements, but that doesn't hurt. My sample class had a Name property on the Order class.
The downside of this approach is that you have to implement the serialization of the OrdersContainer class manually, including the deserialization.
Linq to XML
The second option is to use Linq to XML for the serialization part. You'd also have to implement the serialization manually, but you could use the otb XmlSerializer for deserialization (on the other hand you might want to avoid to mix two different frameworks). You could serialize the OrdersContainer class as follows and also write the sequence number:
var doc = new XDocument(new XElement("OrdersContainer",
new XElement("Orders",
cont.Orders.Select((x, i) => new XElement("Order",
new XAttribute("Sequence", (i + 1).ToString()),
new XElement("Name", x.Name))))));
doc.Save(writer);
This created the following XML:
<?xml version="1.0" encoding="utf-16"?>
<OrdersContainer>
<Orders>
<Order Sequence="1">
<Name>Order 1</Name>
</Order>
<Order Sequence="2">
<Name>Order 2</Name>
</Order>
</Orders>
</OrdersContainer>
Please note that the sample uses an overload of the Select method that also receives the index of the item so that the sequence number can be created based upon the position in the array.
If order is a class of yours, you can add a property with [XmlAttributeAttribute]
class Order {
[XmlAttribute()]
public int Sequence { get; set; }
But you should set this Sequence property in all items of your list.
this works fine -
var o = new XmlSerializerNamespaces();
o.Add("", "");
var ser = new XmlSerializer(typeof(List<Order>), "");
using (var sw = new StringWriter())
{
ser.Serialize(sw, new List<Order> {
new Order { sequence = "1", MyProperty = 1 },
new Order { sequence = "2", MyProperty = 2 } },
o);
var t = sw.ToString();
}
AND
[XmlAttribute(AttributeName = "sequence", DataType = "string")]
public string sequence { get; set; }

How to omit XML element in C# XML generated from XSD?

Minoccurs is 0 in the XSD and nillable is true for an element.
But if I don't set the element value, it takes it as null and the record is blanked out on the server. Is there a way to tell it to omit the element from the output XML when some conditions are satisfied but have it for other cases?
<xs:element name='CLS_CD' minOccurs='0' nillable='true' type='xdv:stringLen20'/>
If you are using XmlSerializer, you can control whether the value is emitted by including a PropertyNameSpecified property.
Another option is to use a special
pattern to create a Boolean field
recognized by the XmlSerializer, and
to apply the XmlIgnoreAttribute to the
field. The pattern is created in the
form of propertyNameSpecified. For
example, if there is a field named
"MyFirstName" you would also create a
field named "MyFirstNameSpecified"
that instructs the XmlSerializer
whether to generate the XML element
named "MyFirstName".
For example, if you declare the class like this:
public class Data
{
[XmlIgnore]
public bool CLS_CDSpecified { get; set; }
[XmlElement(IsNullable=true)]
public string CLS_CD { get; set; }
}
Then you can serialize nothing, an explicit nil value, or an actual value:
var serializer = new XmlSerializer(typeof(Data));
var serializesNothing = new Data();
serializesNothing.CLS_CD = null;
serializesNothing.CLS_CDSpecified = false;
serializer.Serialize(Console.Out, serializesNothing);
Console.WriteLine();
Console.WriteLine();
var serializesNil = new Data();
serializesNil.CLS_CD = null;
serializesNil.CLS_CDSpecified = true;
serializer.Serialize(Console.Out, serializesNil);
Console.WriteLine();
Console.WriteLine();
var serializesValue = new Data();
serializesValue.CLS_CD = "value";
serializesValue.CLS_CDSpecified = true;
serializer.Serialize(Console.Out, serializesValue);
Output:
<?xml version="1.0" encoding="IBM437"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
<?xml version="1.0" encoding="IBM437"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CLS_CD xsi:nil="true" />
</Data>
<?xml version="1.0" encoding="IBM437"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CLS_CD>value</CLS_CD>
</Data>

Read typed objects from XML using known XSD

I have the following (as an example) XML file and XSD.
<?xml version="1.0" encoding="utf-8" ?>
<foo>
<DateVal>2010-02-18T01:02:03</DateVal>
<TimeVal>PT10H5M3S</TimeVal>
</foo>
and
version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="foo">
<xs:complexType>
<xs:sequence>
<xs:element name="DateVal" type="xs:dateTime" />
<xs:element name="TimeVal" type="xs:duration" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Then the following C# code:
static void Main(string[] args)
{
XmlDocument xd = new XmlDocument();
XmlSchema xs;
using (var fs = File.OpenRead(FilePath + "SimpleFields.xsd"))
{
xs = XmlSchema.Read(fs, null);
}
xd.Schemas.Add(xs);
xd.Load((FilePath + "SimpleFields.xml"));
xd.Validate(null);
var el_root = xd.DocumentElement;
var el_date = (XmlElement)el_root.SelectSingleNode("./DateVal");
//WANTED: el_date.Value = 2010-02-18 01:02:03 (as a DateTime Object)
//ACTUAL: el_date.InnerText="2010-02-18T01:02:03"
var el_duration = (XmlElement)el_root.SelectSingleNode("./TimeVal");
//WANTED: el_date.Value = 10 hours, 5 minutes, 3 seconds (as a TimeSpan Object)
//ACTUAL: el_date.InnerText="PT10H5M3S"
Console.WriteLine("DONE");
Console.ReadLine();
}
How can I read the data as strongly typed objects ?
I will be targetting a WindowsMobile device, but this shouldn't need to affect the answer too much. (can be .NET 2.0 or 3.5 ... Not sure if Sstem.Xml.Linq will help or not)
You need to do two steps:
1) Take your XML schema file and run it through the xsd.exe utility (which comes with the Windows SDK - it's in C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\ or some similar path. This can turn the XSD file into a C# class:
xsd /c yourfile.xsd
This should give you a file yourfile.cs which contains a class representing that XML schema.
2) Now, armed with that C# class, you should be able to just deserializing the XML file into an instance of your new object:
XmlSerializer ser = new XmlSerializer(typeof(foo));
string filename = Path.Combine(FilePath, "SimpleFields.xml");
foo myFoo = ser.Deserialize(new FileStream(filename, FileMode.Open)) as foo;
if (myFoo != null)
{
// do whatever you want with your "foo"
}
That's about as simple as it gets! :-)
OK - Found the answer I was looking for.
it is the XmlConvert class.
var el_date = (XmlElement)el_root.SelectSingleNode("./DateVal");
//WANTED: el_date.Value = 2010-02-18 01:02:03 (as a DateTime Object)
var val_date = XmlConvert.ToDateTime(el_date.InnerText);
//ACTUAL: el_date.InnerText="2010-02-18T01:02:03"
var el_duration = (XmlElement)el_root.SelectSingleNode("./TimeVal");
//WANTED: el_date.Value = 10 hours, 5 minutes, 3 seconds (as a TimeSpan Object)
var val_duration = XmlConvert.ToTimeSpan(el_duration.InnerText);
//ACTUAL: el_date.InnerText="PT10H5M3S"
Marc's answer was correct in terms of reading in a whole strongly-typed class, but in this case I only wanted to read a single strongly-typed element/node.

Categories