Writing XML records one at a time - c#

I am working with a very large data set so need to write records to an xml file one at a time.
I have tried this code:
using (FileStream retryFile = System.IO.File.Create(filePath))
{
var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var ser = new XmlSerializer(typeof(Record));
foreach(var item in veryLargeEnumerable)
{
ser.Serialize(retryFile, item, emptyNamespaces);
}
}
This writes the record but between every record it adds:
<?xml version="1.0"?>
When I try to read the file I get invalid format error.
How can I serialize objects one at a time to XML?

The code shown in your question generates an invalid xml hierarchy;
it doesn't contain a single root element, as each <Record> gets written at the root level, eg.:
<?xml version="1.0"?>
<Record>
<Name>foo</Name>
</Record><?xml version="1.0"?>
<Record>
<Name>bar</Name>
</Record><?xml version="1.0"?>
<Record>
<Name>baz</Name>
</Record>
You'll have to construct a single root element first.
Use an XmlWriter to do so, and then use that same XmlWriter as target to serialize the records into.
using (FileStream retryFile = System.IO.File.Create(filePath))
using (var xmlWriter = XmlWriter.Create(retryFile))
{
xmlWriter.WriteStartElement("Records");
var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var ser = new XmlSerializer(typeof(Record));
/* Your loop goes here. */
ser.Serialize(xmlWriter, new Record { Name = "foo" }, emptyNamespaces);
ser.Serialize(xmlWriter, new Record { Name = "bar" }, emptyNamespaces);
ser.Serialize(xmlWriter, new Record { Name = "baz" }, emptyNamespaces);
xmlWriter.WriteEndElement();
}
public class Record
{
public string Name {get; set; }
}
The xml wil then look like below, having a single root element <Records> containing the <Record> child elements.
<?xml version="1.0" encoding="utf-8"?>
<Records>
<Record><Name>foo</Name></Record>
<Record><Name>bar</Name></Record>
<Record><Name>baz</Name></Record>
</Records>

Related

Serialize an Object into XML Array

I have a scenario where I may need to serialize an object as a root element into an XML file or if a file is provided that already has an array of objects I need to serialize the object into the array.
Because the object may be a root I have decorated it with the System.Xml.Serialization.XmlRootAttribute.
When I serialize the object to an XElement I get the root version of the object. When I add the XElement to the array it retains the namespace attributes and can not be deserialized properly.
Here is a portion of the class:
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:www.agxml.org:schemas:all:4:0", TypeName = "CommodityMovement")]
[System.Xml.Serialization.XmlRootAttribute(ElementName ="CommodityMovement", Namespace="urn:www.agxml.org:schemas:all:4:0", IsNullable=false)]
public partial class CommodityMovementType {
...
}
When I create a file with a list it works just fine.
private static String CreateMessageFile<T>(String filePath, T message)
{
var xmlDoc = new XDocument();
// serialize XML to string to the xmlEntity document
using (var writer = new StringWriter())
{
var entitySerializer = new XmlSerializer(typeof(List<T>));
var list = new List<T>();
list.Add(message);
entitySerializer.Serialize(writer, list);
xmlDoc.Add(XElement.Parse(writer.ToString()));
}
xmlDoc.Save(filePath);
}
This will serialize an array with one message:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCommodityMovement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CommodityMovement>
...
</CommodityMovement>
</ArrayOfCommodityMovement>
However, I may read that file and add a message to that array.
private static String CreateMessageFile<T>(String filePath, T message)
{
var xmlDoc = new XDocument();
using var sourceStream = File.Open(filePath,FileMode.Open);
xmlDoc.Add(XElement.Load(sourceStream));
using (var writer = new StringWriter())
{
var entitySerializer = new XmlSerializer(entity.DotNetType);
// entitySerializer.Serialize(writer, list);
entitySerializer.Serialize(writer, message);
xmlDoc.Root.Add(XElement.Parse(writer.ToString()));
}
xmlDoc.Save(filePath);
}
This produces the the following XML:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCommodityMovement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CommodityMovement>
...
</CommodityMovement>
<CommodityMovement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:www.agxml.org:schemas:all:4:0">
...
</CommodityMovement>
</ArrayOfCommodityMovement>
When I attempt to deserialize the ArrayOfCommodityMovement it only deserializes the first CommodityMovement message.
If I open the generated XML file and remove the namespace attributes from the second CommodityMovement element then it will deserialize correctly. Here is the test I am using to define "correctly".
XDocument xDocument = XDocument.Load(filePath);
var descendants = xDocument.Descendants("CommodityMovement");
Assert.Equal(2, descendants.Count());
var entitySerializer = new XmlSerializer(typeof(List<CommodityMovementType>));
var commodityMovementList = entitySerializer.Deserialize(xDocument.CreateReader()) as List<CommodityMovementType>;
Assert.NotEmpty(commodityMovementList);
Assert.Equal(2, commodityMovementList.Count);
So how can I deserialize the object and insert the resulting element into an existing array and make sure the attributes aren't added?
BTW, I need to keep the System.Xml.Serialization.XmlRootAttribute because depending on configuration I need to be able to generate one message per file and the CommodityMovement then becomes a root element.
Note:
This isn't my exact code. It is a simplified example.
Thanks in advance for any help.
I found the solution is to remove the attributes and namespace if this is not the first element in the array. To see if it is the first I check to see if the document root is null:
if (xmlDoc.Root is null)
If it is not null, I serialize the element, remove it's attributes and namespace and add the element to the root element:
else
{
var element = SerializeEntityAsElement(entity, masterData);
// remove attributes and namespace so element can be added to existing array
element.RemoveAttributes();
element.Name = element.Name.LocalName;
xmlDoc.Root.Add(element);
}
Now my test will pass:
XDocument xDocument = XDocument.Load(filePath);
var descendants = xDocument.Descendants("CommodityMovement");
Assert.Equal(2, descendants.Count());
var entitySerializer = new XmlSerializer(typeof(List<CommodityMovementType>));
var commodityMovementList = entitySerializer.Deserialize(xDocument.CreateReader()) as List<CommodityMovementType>;
Assert.NotEmpty(commodityMovementList);
Assert.Equal(2, commodityMovementList.Count);

XML node name and attributes are not available

I have the following XML structure:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<values>
<bool key="Chapter_1.Boolean1.value">true</bool>
<string key="Chapter_1.Text1.value">abc</string>
<string key="Chapter_1.Text2.value">Inspection done (2)</string>
<number key="Chapter_1.Number1.value">128</number>
<number key="Chapter_1.Number2.value">34539718</number>
<number key="Chapter_1.Number3.value">3</number>
<datetime key="Chapter_2.Chapter_2_1.DateTime1.value">2020-06-02T09:00:00+03:00</datetime>
<datetime key="Chapter_2.Chapter_2_1.DateTime2.value">2016-02-05T00:00:00+02:00</datetime>
<string key="Chapter_3.Text4.value">52</string>
<string key="Chapter_3.Text5.value">22</string>
<number key="Chapter_3.Number6.value">34539718</number>
</values>
and the following C# code:
var settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Auto;
settings.IgnoreWhitespace = true;
settings.IgnoreComments = true;
using (var xmlReader = new XmlTextReader(xmlFilePath))
{
while (xmlReader.Read())
{
var nodeName = xmlReader.Name;
var attrName = xmlReader.GetAttribute("key");
}
}
The problem is that the node name is empty and there are no attributes for the following keys:
Chapter_1.Text1.value
Chapter_1.Number1.value
Chapter_3.Text5.value
Anyone has any idea what could be the problem?
The code worked well here, I was able to access all xml tags and attributes.
Maybe you're confused because at each xmlReader.Read() it reads only one part of the tag. So to read all the tag with key "Chapter_1.Text1.value", first it reads a tag with name string and key "Chapter_1.Text1.value", then it reads something without a name, without a attribute, but with value "abc" and then it reads the tag closing with name string, but no attribute and no value.
It would be easier to use Xml to Linq :
var xml = XDocument.Load(__PATH_TO_XML__);
var values = xml.XPathSelectElements("/values/*")
.Select(x => new
{
Type = x.Name,
Key = x.Attribute("key"),
Value = x.Value
});
If you want to read the value as well, try this
using (var xmlReader = new XmlTextReader(#"yourxmlfile"))
{
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element)
{
var nodeName = xmlReader.Name;
var attrName = xmlReader.GetAttribute("key");
Console.WriteLine(nodeName);
Console.WriteLine(attrName);
}
if (xmlReader.NodeType==XmlNodeType.Text)
{
Console.WriteLine(xmlReader.Value);
}
}
}

How to get element by element from XmlDocument

I was able to get my first element from this list
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetITARListResponse xmlns="http://tempuri.org/">
<GetITARListResult>
<ClassificationCode>
<code>dsd</code>
<description>toto</description>
<legislation>d/legislation>
</ClassificationCode>
<ClassificationCode>
<code>dsd</code>
<description>tata</description>
<legislation>dsd</legislation>
</ClassificationCode>
<ClassificationCode>
<code>code2</code>
<description>dsds</description>
<legislation>dsd</legislation>
</ClassificationCode>
by writing
XDocument result = new XDocument();
result = ExportControl.ResultXML;
var codes = HttpContext.Current.Server.MapPath("~/XML_Templates/codes.xml");
dynamic root = new ExpandoObject();
XmlToDynamic.Parse(root, xDoc.Elements().First());
var result = xDoc.Descendants(XNamespace.Get("http://tempuri.org/") + "code").First();
which get the the first code "dsd". But what if I want a foreach and get all the code? or what if I want a certain code ? for example
var result = xDoc.Descendants(XNamespace.Get("http://tempuri.org/") + "code")[2]
.Net framework provides a set of tools you can use to move over an XML file:
XmlReader: this class reads xml files in hierarchical manner and only forward
without cached.
XmlWriter: this class writes in xml files only forward without
cached.
XmlDocument: it helps you to navigate and edit small documents as it's
slower than XmlReader/Writer. It uses XmlNode objects to move through your document and perform changes (attributes). After editing your you can save. A nifty way to navigate is by using XPath
(query language for xml).
XPathNvigator: class offers an easy way to navigate through an XML document.
In your case, my recommendation is to implement a couple of methods one for iteration and one for location of a particular node with XmlReader and XPath respectively.
Update: the XML example is malformed here:
<legislation>d/legislation>
This shows you an example to read the file:
using System;
using System.Xml;
namespace XMLTest
{
class Program
{
static void Main(string[] args)
{
string xml = #"<?xml version=""1.0"" encoding=""utf-8"" ?>
<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<soap:Body>
<GetITARListResponse xmlns=""http://tempuri.org/"">
<GetITARListResult>
<ClassificationCode>
<code>dsd</code>
<description>toto</description>
<legislation>d</legislation>
</ClassificationCode>
<ClassificationCode>
<code>dsd</code>
<description>tata</description>
<legislation>dsd</legislation>
</ClassificationCode>
<ClassificationCode>
<code>code2</code>
<description>dsds</description>
<legislation>dsd</legislation>
</ClassificationCode>
</GetITARListResult>
</GetITARListResponse>
</soap:Body>
</soap:Envelope>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var items = doc.GetElementsByTagName("ClassificationCode");
foreach (var item in items)
{
Console.WriteLine(((System.Xml.XmlElement)(item)).InnerText.ToString());
}
Console.ReadLine();
}
}
}
// OUTPUT
// dsdtotod
// dsdtatadsd
// code2dsdsdsd
Your code sample is returning the First item merely because you're calling First() method. In order to loop through all codes you can skip the call and you will get an IEnumerable. Then you can loop like this:
var codes = result.Descendants(XNamespace.Get("http://tempuri.org/") + "code");
foreach (var codeElement in codes)
{
Console.WriteLine(codeElement.Value);
}
// prints:
// dsd
// dsd
// code2
To access them by index you can use ElementAt like this:
var someCode = codes.ElementAt(1);
Console.WriteLine(someCode.Value); // prints dsd
Or you can filter by name like this:
var code2 = codes.Where(c => c.Value == "code2").First();
Console.WriteLine(code2.Value); // prints code2

xml c# read and write helper

I have the following examples, and the c# is just my draft. Can you show me how to call the xml file and read there so I can get the value
public static ArrayList GetLocationLiabilityAmount()
{
ArrayList al = new ArrayList();
string selectedValue = Library.MovieClass.generalLibailityLocationLiability;
if (!String.IsNullOrEmpty(selectedValue))
{
if (option from xml == selectedValue)
{
al.Add(minvalue);
al.Add(maxvalue);
}
return al;
}
else
{
return null;
}
}
XML:
<?xml version="1.0" encoding="utf-8" ?>
<AccidentMedicalCoverage>
<coverage option="1" value="10000" showvalue="$10,000 per person"></coverage>
<coverage option="2" value="25000" showvalue="$25,000 per person"></coverage>
<coverage option="3" value="50000" showvalue="$50,000 per person"></coverage>
</AccidentMedicalCoverage>
The question is not too clear but this is what I assume you want:
Given an option if you want to get the value from the XML this is one way you can do it:
XmlDocument xDoc = new XmlDocument();
xDoc.Load("c:\\xmlfile\\coverage.xml");
// Select the node with option=1
XmlNode node = xDoc.SelectSingleNode("/AccidentMedicalCoverage/coverage[#option='1']");
// Read the value of the Attribute 'value'
var value = node.Attributes["value"].Value;
I prefer linq to Xml. There are two ways given to get the data into an XDocument shown below and then a basic query into the data
//var xml = File.ReadAllText(#"C:\data.xml");
var xml = GetFile();
//var xDoc = XDocument.Load(#"C:\data.xml"); Alternate
var xDoc = XDocument.Parse(xml);
var coverages = xDoc.Descendants("coverage");
coverages.Select (cv => cv.Attribute("showvalue").Value)
.ToList()
.ForEach(showValue => Console.WriteLine (showValue));
/* Output
$10,000 per person
$25,000 per person
$50,000 per person
*/
...
public string GetFile()
{
return #"<?xml version=""1.0"" encoding=""utf-8"" ?>
<AccidentMedicalCoverage>
<coverage option=""1"" value=""10000"" showvalue=""$10,000 per person""></coverage>
<coverage option=""2"" value=""25000"" showvalue=""$25,000 per person""></coverage>
<coverage option=""3"" value=""50000"" showvalue=""$50,000 per person""></coverage>
</AccidentMedicalCoverage>";
}

Serialize XML and append new elements to XML file

I have created a XML class using XSD2Code from my XML Schema. The class having a method SaveToFile. So when I am trying to add new elements and save it to the XML file it overwrites the entire content.
Does anyone have idea how to insert elements(append to the XML) to the XML file via Serialization.
eg:
<?xml version="1.0" encoding="utf-8"?>
<root>
<element>content1</element>
</root>
If this is the XML file I need to add an element and the resulting should come as like below using Serialization.
<?xml version="1.0" encoding="utf-8"?>
<root>
<element>content1</element>
<element>content2</element>
</root>
Your request would seem to be resolvable by adding another item into your List collection.
MyTypeFromXmlSchema myType = new MyTypeFromXmlSchema();
myType.MyElementItems = new List<MyElementItem>();
myType.MyElementItems.Add { new MyElementItem { value = "value1" } };
myType.MyElementItems.Add { new MyElementItem { value = "value2" } };
Then serialize type using XmlSerializer.

Categories

Resources