I have many .xsd files for many xml schemas
example
XML 1.0 - xml_1_0.xml
<?xml version="1.0" encoding="UTF-8"?>
<cars version="1.00">
<car>Honda</car>
<car>Ferrari</car>
</cars>
XML 2.0 - xml_2_0.xml
<?xml version="1.0" encoding="UTF-8"?>
<cars version="2.00">
<car>
<name>Honda</name>
<color>White</color>
</car>
<car>
<name>Honda</name>
<color>Red</color>
</car>
</cars>
I create my classes from .xsd like this
xsd.exe cars_1_0.xsd /c
xsd.exe cars_2_0.xsd /c
And Deserialize like this:
foreach(string file in files) {
XmlDocument doc = new XmlDocument();
doc.Load(file);
string version = doc.SelectSingleNode("/Cars/#version").Value;
if(version == "1.00")
{
Stream reader = new FileStream(file, FileMode.Open);
XmlSerializer serializer = new XmlSerializer(typeof(v1.Cars));
v1.Cars XML = new v1.Cars();
XML = (v1.Cars)serializer.Deserialize(reader);
}
else if(version == "2.00")
{
Stream reader = new FileStream(file, FileMode.Open);
XmlSerializer serializer = new XmlSerializer(typeof(v2.Cars));
v2.Cars XML = new v2.Cars();
XML = (v2.Cars)serializer.Deserialize(reader);
}
}
Does anyone know a better way to do this, or have a better performance?
You have several options, depending on how far you want to take this. One fairly non invasive option would be to not use XmlDocument and avoid loading the stream more than once. For example, your existing code could be simplified/streamlined to :
foreach (string file in files)
{
using (var stream = new FileStream(file, FileMode.Open))
{
var settings = new XmlReaderSettings();
settings.CloseInput = false;
string version = "";
using (var xmlReader = XmlReader.Create(stream))
{
if (xmlReader.ReadToFollowing("Cars"))
{
version = xmlReader.GetAttribute("version");
}
else
{
throw new XmlException("Could not get 'version' attribute of 'Cars' root element!");
}
}
stream.Position = 0;
if (version == "1.00")
{
XmlSerializer serializer = new XmlSerializer(typeof(v1.Cars));
v1.Cars XML = new v1.Cars();
XML = (v1.Cars)serializer.Deserialize(stream);
}
else if (version == "2.00")
{
XmlSerializer serializer = new XmlSerializer(typeof(v2.Cars));
v2.Cars XML = new v2.Cars();
XML = (v2.Cars)serializer.Deserialize(stream);
}
}
}
Since you're just reading off the root element, you might even be able to get away with deserializing from the XmlReader and not have to reset the position on the FileStream.
This avoids the overhead of loading the entire file twice (once for XmlDocument, then again for XmlSerializer) - and particularly avoids the memory overhead of creating a DOM for each document.
A more nuclear option would be implementing IXmlSerializable on a set of custom classes, which would have custom logic in the ReadXml methods to parse the version attribute and instantiate the correct child type(s) - e.g. a CarCollection class that has a List<Car> property, where Car is an abstract class that has CarV1 and CarV2 as descendants. This would be about as efficient as you could get (and offer very fine grained control over your class hierarchy design), but would eliminate the possibility of using xsd.exe to generate your classes.
Related
I want to get an object from an xml file. In my example I am using iso 2002 pain.001.001.03
I have downloaded the schema from
pain.001.001.03.xsd
and the xml file from
pain.001.001.03.xml
I have validated my xml against the xsd using this tool
Validate XML
I have generated a class using xsd
and I am using the code below in order to deserialize
XmlSerializer ser = new XmlSerializer(typeof(CustomerCreditTransferInitiationV03), new XmlRootAttribute
{
ElementName = "Document",
Namespace = "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03",
});
FileStream myFileStream = new FileStream("C:\\001.001.03\\pain.001.001.03.xml", FileMode.Open);
CustomerCreditTransferInitiationV03 myObject = (CustomerCreditTransferInitiationV03) ser.Deserialize(myFileStream);
The code return null values but my xml has values
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03">
<CstmrCdtTrfInitn>
The root element is an Document, and not a CstmrCdtTrfInitn :
var serializer = new XmlSerializer(typeof(Document));
using (var file = File.OpenRead(path))
{
var document = (Document)serializer.Deserialize(file);
var transfer = document.CstmrCdtTrfInitn;
}
I am using XmlSerializer in C# to generate an XML document based on a model. I need to generate the following XML root that contains a duplicate namespace using separate prefixes. Below is what the output should look likeā¦
<ClinicalDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:hl7-org:v3 CDA_SDTC.xsd"
xmlns="urn:hl7-org:v3"
xmlns:cda="urn:hl7-org:v3"
xmlns:sdtc="urn:hl7-org:sdtc">
However, when I Serialize this, the default entry is removed (which contains the duplicate namespace) and the root is prefixed.
<cda:ClinicalDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sdtc="urn:hl7-org:sdtc" xsi:schemaLocation="http://www.w3.org/2001/XMLSchema-instance" xmlns:cda="urn:hl7-org:v3">
Here is my XmlSerializer code...
var writer = new XmlSerializer(clinicalDocument.GetType(),"urn:hl7-org:v3");
var myNamespace = new XmlSerializerNamespaces();
myNamespace.Add("sdtc", "urn:hl7-org:sdtc");
myNamespace.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");
myNamespace.Add("cda", "urn:hl7-org:v3");
using (var file = new System.IO.StreamWriter(CCDUncOutputPath))
{
writer.Serialize(file, clinicalDocument, myNamespace);
file.Close();
};
writer = null;
GC.Collect();
Does anyone have a fix for this?
I have a problem when I deserialize the xml into List of Objects. I searched it on the net this morning, but my problem isn't resolved.
Deserialization method
public static List<FileAction> DeSerialization()
{
XmlRootAttribute xRoot=new XmlRootAttribute();
xRoot.ElementName="ArrayOfSerializeClass";
xRoot.IsNullable=true;
XmlSerializer serializer = new XmlSerializer(typeof(List<FileAction>),xRoot);//, new XmlRootAttribute("ArrayOfSerializeClass")
using (Stream streamReader = File.OpenRead(#"C:\serialization\SerializationWithFileWatcher\Output\XmlSerialize.xml"))//FileStream fs =new FileStream(xmlPath,FileMode.Open)
{
using (XmlReader reader = XmlReader.Create(streamReader))
{
int count =0;
List<FileAction> serialList2 = (List<FileAction>)serializer.Deserialize(reader);
return (List<FileAction>)serializer.Deserialize(reader);
}
}
Calling Method
String resultPath = #"C:\serialization\SerializationWithFileWatcher\Output\XmlSerialize.xml";
if (!File.Exists(resultPath))
{
XmlSerializer xs = new XmlSerializer(typeof(List<SerializeClass>));
using (FileStream fileStream = new FileStream(#"C:\serialization\SerializationWithFileWatcher\Output\XmlSerialize.xml", FileMode.Create))
{
xs.Serialize(fileStream, serializeList);//seri
fileStream.Close();
}
Console.WriteLine("Succesfully serialized to XML");
}
else
{
//string path= #"C:\serialization\SerializationWithFileWatcher\Output\XmlSerialize.xml";
DeSerialization();
XmlSerializer xs = new XmlSerializer(typeof(List<SerializeClass>));
FileStream fs = new FileStream(#"C:\serialization\SerializationWithFileWatcher\Output\XmlSerialize.xml", FileMode.Append, FileAccess.Write);
using (XmlWriter xwr = XmlWriter.Create(fs))//TextWriter xwr = new StreamWriter
{
xs.Serialize(xwr, serializeList);//seri
//fs.Close();
}
Console.WriteLine("Succesfully serialized to XML");
}
return serializeList;
The reason why I am calling it here is that I want to add this object again to the xml file.
THe error is that here is an error in XML document (15,27).
My Xml structure
<?xml version="1.0"?>
<ArrayOfSerializeClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SerializeClass>
<creationTime>2013-11-25T09:53:25.3325289+05:30</creationTime>
<fileAction>Renamed</fileAction>
<Properties>
<FileAttributes fileName="validate json.txt">
<fileSize>307</fileSize>
<extension>.txt</extension>
<lastAccessTime>2013-11-25T09:53:25.3325289+05:30</lastAccessTime
<fullPath>C:\serialization\SerializationWithFileWatcher\SerializationWithFileWatcherProj\validate json.txt</fullPath>
</FileAttributes>
</Properties>
</SerializeClass>
</ArrayOfSerializeClass>
What I understand from the code above is that you are trying to extend the current XML, by first reading it as a FileStream and then using an XmlWriter to add some more content to it.
If my understanding is correct, then you are trying to write to the end of an existing XML file, which is not allowed since any XML document can have only one root node. In your case, that root node is ArrayOfSerializeClass.
So, in order to successfully achieve your task, you must append your XML within the root node.
Update:
Possible solution here: how to append a xml file in c#?
I have an XML file with no root. I cannot change this. I am trying to parse it, but XDocument.Load won't do it. I have tried to set ConformanceLevel.Fragment, but I still get an exception thrown. Does anyone have a solution to this?
I tried with XmlReader, but things are messed up and can't get it work right. XDocument.Load works great, but if I have a file with multiple roots, it doesn't.
XmlReader itself does support reading of xml fragment - i.e.
var settings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment };
using (var reader = XmlReader.Create("fragment.xml", settings))
{
// you can work with reader just fine
}
However XDocument.Load does not support reading of fragmented xml.
Quick and dirty way is to wrap the nodes under one virtual root before you invoke the XDocument.Parse. Like:
var fragments = File.ReadAllText("fragment.xml");
var myRootedXml = "<root>" + fragments + "</root>";
var doc = XDocument.Parse(myRootedXml);
This approach is limited to small xml files - as you have to read file into memory first; and concatenating large string means moving large objects in memory - which is best avoided.
If performance matters you should be reading nodes into XDocument one-by-one via XmlReader as explained in excellent #Martin-Honnen 's answer (https://stackoverflow.com/a/18203952/2440262)
If you use API that takes for granted that XmlReader iterates over valid xml, and performance matters, you can use joined-stream approach instead:
using (var jointStream = new MultiStream())
using (var openTagStream = new MemoryStream(Encoding.ASCII.GetBytes("<root>"), false))
using (var fileStream =
File.Open(#"fragment.xml", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var closeTagStream = new MemoryStream(Encoding.ASCII.GetBytes("</root>"), false))
{
jointStream.AddStream(openTagStream);
jointStream.AddStream(fileStream);
jointStream.AddStream(closeTagStream);
using (var reader = XmlReader.Create(jointStream))
{
// now you can work with reader as if it is reading valid xml
}
}
MultiStream - see for example https://gist.github.com/svejdo1/b9165192d313ed0129a679c927379685
Note: XDocument loads the whole xml into memory. So don't use it for large files - instead use XmlReader for iteration and load just the crispy bits as XElement via XNode.ReadFrom(...)
The only in-memory tree representations in the .NET framework that can deal with fragments are the XmlDocumentFragment in .NET's DOM implementation so you would need to create an XmlDocument and a fragment with e.g.
XmlDocument doc = new XmlDocument();
XmlDocumentFragment frag = doc.CreateDocumentFragment();
frag.InnerXml = stringWithXml; // for instance
// frag.InnerXml = File.ReadAllText("fragment.xml");
or is XPathDocument where you can create one using an XmlReader with ConformanceLevel set to Fragment:
XPathDocument doc;
using (XmlReader xr =
XmlReader.Create("fragment.xml",
new XmlReaderSettings()
{
ConformanceLevel = ConformanceLevel.Fragment
}))
{
doc = new XPathDocument(xr);
}
// new create XPathNavigator for read out data e.g.
XPathNavigator nav = doc.CreateNavigator();
Obviously XPathNavigator is read-only.
If you want to use LINQ to XML then I agree with the suggestions made that you need to create an XElement as a wrapper. Instead of pulling in a string with the file contents you could however use XNode.ReadFrom with an XmlReader e.g.
public static class MyExtensions
{
public static IEnumerable<XNode> ParseFragment(XmlReader xr)
{
xr.MoveToContent();
XNode node;
while (!xr.EOF && (node = XNode.ReadFrom(xr)) != null)
{
yield return node;
}
}
}
then
XElement root = new XElement("root",
MyExtensions.ParseFragment(XmlReader.Create(
"fragment.xml",
new XmlReaderSettings() {
ConformanceLevel = ConformanceLevel.Fragment })));
That might work better and more efficiently than reading everything into a string.
If you wanted to use XmlDocument.Load() then you would need to wrap the content in a root node.
or you could try something like this...
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element)
{
XmlDocument d = new XmlDocument();
d.CreateElement().InnerText = xmlReader.ReadOuterXml();
}
}
XML document cannot have more than one root elements. One root element is required. You may do one thing. Get all the fragment elements and wrap them into a root element and parse it with XDocument.
This would be the best and easiest approach that one could think of.
I am using a class library which represents some of its configuration in .xml. The configuration is read in using the XmlSerializer. Fortunately, the classes which represent the .xml use the XmlAnyElement attribute at which allows me to extend the configuration data for my own purposes without modifying the original class library.
<?xml version="1.0" encoding="utf-8"?>
<Config>
<data>This is some data</data>
<MyConfig>
<data>This is my data</data>
</MyConfig>
</Config>
This works well for deserialization. I am able to allow the class library to deserialize the .xml as normal and the I can use my own XmlSerializer instances with a XmlNodeReader against the internal XmlNode.
public class Config
{
[XmlElement]
public string data;
[XmlAnyElement]
public XmlNode element;
}
public class MyConfig
{
[XmlElement]
public string data;
}
class Program
{
static void Main(string[] args)
{
using (Stream fs = new FileStream(#"c:\temp\xmltest.xml", FileMode.Open))
{
XmlSerializer xser1 = new XmlSerializer(typeof(Config));
Config config = (Config)xser1.Deserialize(fs);
if (config.element != null)
{
XmlSerializer xser2 = new XmlSerializer(typeof(MyConfig));
MyConfig myConfig = (MyConfig)xser2.Deserialize(new XmlNodeReader(config.element));
}
}
}
I need to create a utility which will allow the user to generate a new configuration file that includes both the class library configuration as well my own configuration, so new objects will be created which were not read from the .xml file. The question is how can I serialize the data back into .xml?
I realize that I have to initially call XmlSerializer.Serialize on my data before calling the same method on the class library configuration. However, this requires that my data is represented by an XmlNode after calling Serialize. What is the best way to serialize an object into an XmlNode using the XmlSerializer?
Thanks,
-kevin
btw-- It looks like an XmlNodeWriter class written by Chris Lovett was available at one time from Microsoft, but the links are now broken. Does anyone know of an alternative location to get this class?
So you need to have your class contain custom configuration information, then serialize that class to XML, then make that serialized XML into an XML node: is that right?
Could you just take the string created by the XMLSerializer and wrap that in it's own XML tags?
XmlSerializer xs = new XmlSerializer(typeof(MyConfig));
StringWriter xout = new StringWriter();
xs.Serialize(xout, myConfig);
XmlDocument x = new XmlDocument();
x.LoadXml("<myConfig>" + xout.ToString() + "</myConfig>");
Now x is an XmlDocument containing one element, "<myconfig>", which has your serialized custom configuration in it.
Is that at all what you're looking for?
It took a bit of work, but the XPathNavigator route does work... just remember to call .Close on the XmlWriter, .Flush() doesn't do anything:
//DataContractSerializer serializer = new DataContractSerializer(typeof(foo));
XmlSerializer serializer = new XmlSerializer(typeof(foo));
XmlDocument doc = new XmlDocument();
XPathNavigator nav = doc.CreateNavigator();
XmlWriter writer = nav.AppendChild();
writer.WriteStartDocument();
//serializer.WriteObject(writer, new foo { bar = 42 });
serializer.Serialize(writer, new foo { bar = 42 });
writer.WriteEndDocument();
writer.Flush();
writer.Close();
Console.WriteLine(doc.OuterXml);
One solution is to serialize the inner object to a string and then load the string into a XmlDocument where you can find the XmlNode representing your data and attach it to the outer object.
XmlSerializer xser1 = new XmlSerializer(typeof(Config));
XmlSerializer xser2 = new XmlSerializer(typeof(MyConfig));
MyConfig myConfig = new MyConfig();
myConfig.data = "My special data";
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
XmlWriter xw = new XmlTextWriter(sw);
xser2.Serialize(xw, myConfig);
XmlDocument doc = new XmlDocument();
doc.LoadXml(sb.ToString());
Config config = new Config();
config.data = "some new info";
config.element = doc.LastChild;
xser1.Serialize(fs, config);
However, this solution is cumbersome and I would hope there is a better way, but it resolves my problem for now.
Now if I could just find the mythical XmlNodeWriter referred to on several blogs!
At least one resource points to this as an alternative to XmlNodeWriter: http://msdn.microsoft.com/en-us/library/5x8bxy86.aspx. Otherwise, you could write MS using that form they have on the new MSDN Code Library replacement for GotDotNet looking for XmlNodeWriter.