I have a class that various different XML schemes are created from. I create the various dynamic XDocuments via one (Very long) statement using conditional operators for optional elements and attributes.
I now need to convert the XDocuments back to the class but as they are coming from different schemes many elements and sub elements may be optional. The only way I know of doing this is to use a lot of if statements.
This approach doesn't seem very LINQ and uses a great deal more code than when I create the XDocument so I wondered if there is a better way to do this?
An example would be to get
<?xml version="1.0"?>
<root xmlns="somenamespace">
<object attribute1="This is Optional" attribute2="This is required">
<element1>Required</element1>
<element1>Optional</element1>
<List1>
Optional List Of Elements
</List1>
<List2>
Required List Of Elements
</List2>
</object>
</root>
Into
public class Object()
{
public string Attribute1;
public string Attribute2;
public string Element1;
public string Element2;
public List<ListItem1> List1;
public List<ListItem2> List2;
}
In a more LINQ friendly way than this:
public bool ParseXDocument(string xml)
{
XNamespace xn = "somenamespace";
XDocument document = XDocument.Parse(xml);
XElement elementRoot = description.Element(xn + "root");
if (elementRoot != null)
{
//Get Object Element
XElement elementObject = elementRoot.Element(xn + "object");
if(elementObject != null)
{
if(elementObject.Attribute(xn + "attribute1") != null)
{
Attribute1 = elementObject.Attribute(xn + "attribute1");
}
if(elementObject.Attribute(xn + "attribute2") != null)
{
Attribute2 = elementObject.Attribute(xn + "attribute2");
}
else
{
//This is a required Attribute so return false
return false;
}
//If, If/Elses get deeper and deeper for the next elements and lists etc....
}
else
{
//Object is a required element so return false
return false;
}
}
else
{
//Root is a required element so return false
return false;
}
return true;
}
Update: Just to clarify the ParseXDocument method is inside the "Object" class. Every time an xml document is received the Object class instance has some or all of it's values updated.
Have a look at XElement and XAttribute Type Conversions.
The document root is always non-null.
Validate the object afterwards, so you don't have to implement validation twice (once when parsing and once before saving).
Code:
private static readonly XNamespace xn = "somenamespace";
public bool ParseXDocument(string xml)
{
XDocument document = XDocument.Parse(xml);
var obj = document.Root.Element(xn + "object");
if (obj == null)
return false;
Attribute1 = (string)obj.Attribute("attribute1");
Attribute2 = (string)obj.Attribute("attribute2");
Element1 = (string)obj.Element(xn + "element1");
Element2 = (string)obj.Elements(xn + "element1").ElementAtOrDefault(1);
// ...
return Validate();
}
Below I have a couple of extension methods you can use that may help reach your desired readability. The code in it's current form does not support returning true/false depending on if all the required elements were found. I recommend adopting dtb's advice in that regard, and just parse out the details, and then perform validation on that.
There are downsides, such as repeated iteration over the XML sturcture, but with a bit of imagination I'm sure you could overcome that if it becomes an issue.
Here is what MyObject looks like:
public class MyObject
{
public string Attribute1;
public string Attribute2;
public string Element1;
public string Element2;
public List List1;
public List List2;
public void ParseXDocument(string xml)
{
XNamespace xn = "somenamespace";
XDocument document = XDocument.Parse(xml);
XElement elementRoot = document.Root;
elementRoot.MatchElement(xn.GetName("object"), xObject => {
xObject.MatchAttribute("attribute1", (x,a) => this.Attribute1 = (string)a);
xObject.MatchAttribute("attribute2", (x,a) => this.Attribute2 = (string)a);
xObject.MatchElement(xn.GetName("element1"), x => this.Element1 = (string)x);
xObject.MatchElement(xn.GetName("element2"), x => this.Element2 = (string)x);
});
}
}
Here are the extension methods that support it:
public static class XElementExtensions {
public static XElement MatchAttribute(this XElement x, XName name, Action<XElement, XAttribute> action) {
foreach (var a in x.Attributes(name)) {
action(x, a);
}
return x;
}
public static XElement MatchElement(this XElement x, XName name, Action<XElement> action) {
foreach (var child in x.Elements(name)) {
action(child);
}
return x;
}
}
And finally, here is some sample driver code I used in LinqPad to test it out:
void Main()
{
var xml = #"<?xml version=""1.0""?>
<root xmlns=""somenamespace"">
<object attribute1=""This is Optional"" attribute2=""This is required"">
<element1>Required</element1>
<element2>Optional</element2>
<List1>
Optional List Of Elements
</List1>
<List2>
Required List Of Elements
</List2>
</object>
</root>
";
var o = new MyObject();
o.ParseXDocument(xml);
o.Dump();
}
Related
I have a little problem with reading an XML file on C#.
This is my XML file:
<?xml version = "1.0" encoding="ISO8859-1" standalone="yes" ?>
<SITES>
<SITE>
<ERROR_COUNTER>0</ERROR_COUNTER>
<PROD>0</PROD>
<LOGINFO>
<URL>http://site1.com/login</URL>
<LOGIN>login</LOGIN>
<PASSWORD>pass</PASSWORD>
<DELAYMAX>20</DELAYMAX>
</LOGINFO>
<EMAIL>
<TO>dsds#dee.com, dsdsddd#dee.com,dsdds#dee.com</TO>
<SUBJECT></SUBJECT>
<BODY></BODY>
<PATH></PATH>
</EMAIL>
<TESTS>
<TEST>
<URL>http://site1.com/settings</URL>
<DELAYMAX>5</DELAYMAX>
</TEST>
<TEST>
<URL>http://site1.com/faq</URL>
<DELAYMAX>5</DELAYMAX>
</TEST>
<TEST>
<URL>http://site1.com/download-faq-pdf</URL>
<DELAYMAX>5</DELAYMAX>
</TEST>
</TESTS>
</SITE>
<SITE>
<ERROR_COUNTER>0</ERROR_COUNTER>
<PROD>0</PROD>
<LOGINFO>
<URL>http://site2.com/login</URL>
<LOGIN>login2</LOGIN>
<PASSWORD>pass2</PASSWORD>
<DELAYMAX>20</DELAYMAX>
</LOGINFO>
<EMAIL>
<TO>dsds#dee.com, dsdsddd#dee.com,dsdds#dee.com</TO>
<SUBJECT></SUBJECT>
<BODY></BODY>
<PATH></PATH>
</EMAIL>
<TESTS>
<TEST>
<URL>http://site2.com/settings</URL>
<DELAYMAX>5</DELAYMAX>
</TEST>
<TEST>
<URL>http://site2.com/faq</URL>
<DELAYMAX>5</DELAYMAX>
</TEST>
<TEST>
<URL>http://site2.com/download-faq-pdf</URL>
<DELAYMAX>5</DELAYMAX>
</TEST>
</TESTS>
</SITE>
</SITES>
So I would like to be able, in C#, to browse the list of my sites (I can have several dozen of them), for that, I did this:
XmlDocument xml = new XmlDocument();
xml.Load(#"D:\REM\config.xml");
foreach (XmlElement ndSites in xml.SelectNodes("SITES/SITE"))
{
Console.WriteLine(ndSites.GetElementsByTagName("ERROR_COUNTER"));
}
But once I have done this, I don't know how to access my children's values, let alone how to make a second loop on my TESTS, and then access each child TEST and retrieve my URLs.
My goal is being to retrieve for each website the connection information in LOGINFO, then the URLs in TESTS, TEST and for all the SITES present.
I've tried a lot of things, but I haven't found anything that matches what I need.
If someone could help me.
EDIT : This is my class for deserialize the code C#
class XMLParser
{
public XMLParser() { }
public List<Site> readXML(XmlDocument xml)
{
List<Test> tests = new List<Test>();
Test test;
List<Site> sites = new List<Site>();
Site site;
//List<url>;
foreach (XmlElement ndSites in xml.SelectNodes("SITES/SITE"))
{
int idSite = int.Parse(ndSites.Attributes[0].Value);
site = new Site();
int prod = int.Parse(ndSites["PROD"].InnerText);
int error = 0;
int max = 3;
foreach (XmlElement ndError in ndSites.SelectNodes("/ERROR"))
{
error = int.Parse(ndError["COUNTER"].InnerText);
max = int.Parse(ndError["MAX"].InnerText);
}
foreach (XmlElement ndLogin in ndSites.SelectNodes("LOGINFO"))
{
site = new Site(idSite, ndLogin["URL"].InnerText, ndLogin["LOGIN"].InnerText, ndLogin["PASSWORD"].InnerText, error, prod, max);
}
foreach (XmlElement ndTests in ndSites.SelectNodes("TESTS/TEST"))
{
test = new Test(ndTests["URL"].InnerText, int.Parse(ndTests["DELAYMAX"].InnerText));
tests.Add(test);
}
site.lstTest = tests;
sites.Add(site);
}
return sites;
}
But my i have another problem, when i use my Site object, o have six elements, all the URLs of my site 1 and all the url of my site 2.
For exemple with my XML file above, i would therefore have two Test objects of six elements
Simply you can work with reference value, your code if true, but I completed well.
when you want to work with an xml code, you have to know that structure as well.now lock this:
XmlDocument xml = new XmlDocument();
xml.Load(#"D:\REM\config.xml");
foreach (XmlElement ndSites in xml.SelectNodes("your node"))
{
Console.WriteLine(ndSites.GetElementsByTagName("your node"));
}
This method, GetElementsByTagName() get All xml section that you want and you can work with that like:
XmlDocument xml = new XmlDocument();
xml.Load(#"D:\REM\config.xml");
foreach (XmlElement ndSites in xml.SelectNodes("your node"))
{
var xmlNode = ndSites.GetElementsByTagName("your node");
if(xmlNode != null && xmlNode.contain("your node that you want"))
{
foreach(var item in xmlNode.GetElementByTagName(your node that you want))
{
console.write(item.value);
}
}
}
Fore more information, it is good that visit this
I'm sure you can do something like this:
var MiXMLD = new XmlDocument();
MiXMLD.Load(#"C:\path\MiXML.xml");
if (!(MiXMLD == null))
{
XmlElement mXMLERaiz = MiXMLD.DocumentElement;
foreach (XmlNode mXMLN in mXMLERaiz.ChildNodes)
{
// if type is <SITE>
if ((mXMLN.Name == "SITE"))
{
//In this point you can see the attributes
if ((mXMLN.Attributes.Count > 0))
{
foreach (XmlAttribute mAtr in mXMLN.Attributes)
{
if ((mAtr.Name == "Exalmple"))
{
var attr = mAtr.Value;
}
else if ((mAtr.Name == "Example"))
{
var attr = mAtr.Value;
}
}
}
// first node child of element of <parent>
// you already know the type <Type>
XmlElement mXMLchild = (XmlElement)mXMLN.FirstChild;
// aniway check the Type
if ((mXMLchild.Name == "Type"))
{
var mChild = mXMLchild.FirstChild.Value;
}
}
}
}
Of course you have to read every element.
I probably missunderstand what is your problem. Just store your xml data in business class like
public class Site
{
public int ErrorCounter { get; set; }
public LogInfo LogInfos { get; set; }
public Email Email { get; set; }
public List<Test> Tests { get; set; } = new List<Test>();
}
You unserialize your xml inside a list of your class, with the same way you already do. Then you can access all your data
List<Site> siteList = new List<Site>();
XmlDocument xml = new XmlDocument();
xml.Load(#"D:\REM\config.xml");
foreach (XmlElement ndSites in xml.SelectNodes("SITES/SITE"))
{
siteList.ErrorCounter = int.Parse(ndSites["ERROR_COUNTER"].innerText); // You should handle the potential parse error expression
/** do it for all you need */
foreach (XmlElement ndTests in ndSites.SelectNodes("TESTS/TEST"))
{
Test currentTest = new Test();
/** Fill it **/
siteList.Tests.add(currentTest);
}
}
Something about that, i don't have ide for test.
Hope it was your question
You can use these Wrappers. Load the XML and put it into the the Wrapper.
public class TestsXmlWrapper
{
private XmlDocument _xml;
public TestsXmlWrapper(XmlDocument xml)
{
_xml = xml;
}
public IEnumerable<Site> Sites
{
get
{
foreach (XmlElement site in _xml.SelectNodes("SITES/SITE"))
{
yield return new Site(site);
}
}
}
}
public class Site
{
private XmlElement _site;
public Site(XmlElement site)
{
_site = site;
}
public String ErrorCount => _site.SelectSingleNode("ERROR_COUNTER")?.InnerText;
public String LoginUrl => _site.SelectSingleNode("LOGINFO/URL")?.InnerText;
public String Username => _site.SelectSingleNode("LOGINFO/LOGIN")?.InnerText;
public String Password => _site.SelectSingleNode("LOGINFO/PASSWORD")?.InnerText;
public IEnumerable<Test> Test
{
get
{
foreach (XmlElement test in _site.SelectNodes("TESTS/TEST"))
{
yield return new Test(test);
}
}
}
}
public class Test
{
private XmlElement _test;
public Test(XmlElement test)
{
_test = test;
}
public String Url => _test.SelectSingleNode("URL")?.InnerText;
}
...the same for email
When I serialize the value : If there is no value present in for data then it's coming like below format.
<Note>
<Type>Acknowledged by PPS</Type>
<Data />
</Note>
But what I want xml data in below format:
<Note>
<Type>Acknowledged by PPS</Type>
<Data></Data>
</Note>
Code For this i have written :
[Serializable]
public class Notes
{
[XmlElement("Type")]
public string typeName { get; set; }
[XmlElement("Data")]
public string dataValue { get; set; }
}
I am not able to figure out what to do for achieve data in below format if data has n't assign any value.
<Note>
<Type>Acknowledged by PPS</Type>
<Data></Data>
</Note>
You can do this by creating your own XmlTextWriter to pass into the serialization process.
public class MyXmlTextWriter : XmlTextWriter
{
public MyXmlTextWriter(Stream stream) : base(stream, Encoding.UTF8)
{
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
You can test the result using:
class Program
{
static void Main(string[] args)
{
using (var stream = new MemoryStream())
{
var serializer = new XmlSerializer(typeof(Notes));
var writer = new MyXmlTextWriter(stream);
serializer.Serialize(writer, new Notes() { typeName = "Acknowledged by PPS", dataValue="" });
var result = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(result);
}
Console.ReadKey();
}
If you saved your string somewhere (e.g a file) you can use this simple Regex.Replace:
var replaced = Regex.Replace(File.ReadAllText(name), #"<([^<>/]+)\/>", (m) => $"<{m.Groups[1].Value.Trim()}></{m.Groups[1].Value.Trim()}>");
File.WriteAllText(name, replaced);
IMO it's not possibe to generate your desired XML using Serialization. But, you can use LINQ to XML to generate the desired schema like this -
XDocument xDocument = new XDocument();
XElement rootNode = new XElement(typeof(Notes).Name);
foreach (var property in typeof(Notes).GetProperties())
{
if (property.GetValue(a, null) == null)
{
property.SetValue(a, string.Empty, null);
}
XElement childNode = new XElement(property.Name, property.GetValue(a, null));
rootNode.Add(childNode);
}
xDocument.Add(rootNode);
XmlWriterSettings xws = new XmlWriterSettings() { Indent=true };
using (XmlWriter writer = XmlWriter.Create("D:\\Sample.xml", xws))
{
xDocument.Save(writer);
}
Main catch is in case your value is null, you should set it to empty string. It will force the closing tag to be generated. In case value is null closing tag is not created.
Kludge time - see Generate System.Xml.XmlDocument.OuterXml() output thats valid in HTML
Basically after XML doc has been generated go through each node, adding an empty text node if no children
// Call with
addSpaceToEmptyNodes(xmlDoc.FirstChild);
private void addSpaceToEmptyNodes(XmlNode node)
{
if (node.HasChildNodes)
{
foreach (XmlNode child in node.ChildNodes)
addSpaceToEmptyNodes(child);
}
else
node.AppendChild(node.OwnerDocument.CreateTextNode(""))
}
(Yes I know you shouldn't have to do this - but if your sending the XML to some other system that you can't easily fix then have to be pragmatic about things)
You can add a dummy field to prevent the self-closing element.
[XmlText]
public string datavalue= " ";
Or if you want the code for your class then Your class should be like this.
public class Notes
{
[XmlElement("Type")]
public string typeName { get; set; }
[XmlElement("Data")]
private string _dataValue;
public string dataValue {
get {
if(string.IsNullOrEmpty(_dataValue))
return " ";
else
return _dataValue;
}
set {
_dataValue = value;
}
}
}
In principal, armen.shimoon's answer worked for me. But if you want your XML output pretty printed without having to use XmlWriterSettings and an additional Stream object (as stated in the comments), you can simply set the Formatting in the constructor of your XmlTextWriter class.
public MyXmlTextWriter(string filename) : base(filename, Encoding.UTF8)
{
this.Formatting = Formatting.Indented;
}
(Would have posted this as a comment but am not allowed yet ;-))
Effectively the same as Ryan's solution which uses the standard XmlWriter (i.e. there's no need for a derived XmlTextWriter class), but written using linq to xml (XDocument)..
private static void AssignEmptyElements(this XNode node)
{
if (node is XElement e)
{
e.Nodes().ToList().ForEach(AssignEmptyElements);
if (e.IsEmpty)
e.Value = string.Empty;
}
}
usage..
AssignEmptyElements(document.FirstNode);
I have a bunch of C# classes, which are auto generated from an XSD. Then I generate XML files based on those C# classes. Nothing existing so far.
The problem:
The generated XML files are going through validation and the validation requires an extra attribute to all XML tags with xsi:nil="true". Basically the tags should look like : <testTag.01 xsi:nil="true" NV="123123" />, but I can't achieve that in C#. My code is:
if (myObject.TestTag.HasValue)
{
t.testTag01 = new testTag01();
t.testTag01.Value = myObject.TestTag.Value;
}
//else
//{
// t.testTag01 = new testTag01();
// t.testTag01.NV = "123123";//Not Recorded
//}
This code generates <testTag.01>SomeValue</testTag.01> or <testTag.01 xsi:nil="true"/>.
If I uncomment the ELSE, the result would be: <testTag.01>SomeValue</testTag.01> or <testTag.01 NV="123123" />.
So I have no idea how to get to the format, which is required by the validation tool. Any ideas ?
P.S.
Here is the auto-generated C# class:
/// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd",
"4.0.30319.33440")] [System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true,
Namespace="http://www.blabla.org")]
public partial class testTag01 {
private string nvField;
private SomeEnum valueField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string NV {
get {
return this.nvField;
}
set {
this.nvField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public SomeEnum Value {
get {
return this.valueField;
}
set {
this.valueField = value;
}
} }
I wouldn't like to alter that part, but I understand it is impossible without doing it. Also I have tried to set SomeEnum to be Nullable. public SomeEnum? Value, but is throwing an exception:
Cannot serialize member 'Value' of type System.Nullable`1[]. XmlAttribute/XmlText cannot be used to encode complex types.
XmlSerializer doesn't directly support binding to elements that simultaneously have xsi:nil="true" along with other attribute values; see Xsi:nil Attribute Binding Support: The nil attribute and other attributes.
Thus, you need to emit the attribute manually.
If you want to be able to generate an element with no content and two attributes, one named NV and the other always being xsi:nil="true", you can modify your testTag01 class to have the NV property as well as a synthetic property having the correct namespace and name:
public class testTag01
{
[XmlAttribute]
public string NV { get; set; }
[XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Nil { get { return "true"; } set { } }
}
If you sometimes want to have xsi:nil="true" but at other times want the element to have content corresponding to your SomeEnum, you need to do something a bit more complicated, since the xsi:nil="true" must be suppressed when the element has content:
public class testTag01
{
[XmlAttribute]
public string NV { get; set; }
[XmlAttribute("nil", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Nil { get { return SomeEnum == null ? "true" : null; } set { } }
public bool ShouldSerializeNil() { return SomeEnum == null; }
[XmlIgnore]
public SomeEnum? SomeEnum { get; set; }
[XmlText]
public string SomeEnumText
{
get
{
if (SomeEnum == null)
return null;
return SomeEnum.Value.ToString();
}
set
{
// See here if one needs to parse XmlEnumAttribute attributes
// http://stackoverflow.com/questions/3047125/retrieve-enum-value-based-on-xmlenumattribute-name-value
value = value.Trim();
if (string.IsNullOrEmpty(value))
SomeEnum = null;
else
{
try
{
SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, false);
}
catch (Exception)
{
SomeEnum = (SomeEnum)Enum.Parse(typeof(SomeEnum), value, true);
}
}
}
}
}
(An element that simultaneously has both xsi:nil="true" and content would be a violation of the XML standard; hopefully you don't have that.)
Then use it like:
public class TestClass
{
[XmlElement("testTag.01")]
public testTag01 TestTag { get; set; }
public static void Test()
{
Test(new TestClass { TestTag = new testTag01 { NV = "123123" } });
Test(new TestClass { TestTag = new testTag01 { NV = "123123", SomeEnum = SomeEnum.SomeValue } });
}
private static void Test(TestClass test)
{
var xml = test.GetXml();
var test2 = xml.LoadFromXML<TestClass>();
Console.WriteLine(test2.GetXml());
Debug.WriteLine(test2.GetXml());
if (test2.TestTag.NV != test.TestTag.NV)
{
throw new InvalidOperationException("test2.TestTag.NV != test.TestTag.NV");
}
}
}
The XML output looks like:
<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<testTag.01 NV="123123" xsi:nil="true" />
</TestClass>
Or
<TestClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<testTag.01 NV="123123">SomeValue</testTag.01>
</TestClass>
Prototype fiddle using these extension methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString, XmlSerializer serializer = null)
{
T returnValue = default(T);
using (StringReader reader = new StringReader(xmlString))
{
object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
if (result is T)
{
returnValue = (T)result;
}
}
return returnValue;
}
public static string GetXml<T>(this T obj, XmlSerializerNamespaces ns = null, XmlWriterSettings settings = null, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
settings = settings ?? new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(typeof(T))).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
}
As expected there is no solution for that case out of the box, so I improvise a bit and achieved my goal in a post processing logic.
I am parsing the generated XML and if I am looking for a node with xsi:nil attribute, but without NV attribute - I add NV attribute with default value.
Same for the nodes with NV attribute, but no xsi:nil.
Here is the code:
XmlDocument doc = new XmlDocument();// instantiate XmlDocument and load XML from file
doc.Load("somepath.xml");
//Get the nodes with NV attribute(using XPath) and add xsi:nill to that nodes
XmlNodeList nodes = doc.SelectNodes("//*[#NV]");
foreach (XmlNode node in nodes)
{
XmlAttribute nilAttr = doc.CreateAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance");
nilAttr.Value = "true";
node.Attributes.Append(nilAttr);
}
//Get the nodes with xsi:nill attribute(using XPath) and add NV with default value to that nodes
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
XmlNodeList nilNodes = doc.SelectNodes("//*[#xsi:nil]", nsManager);
foreach (XmlNode node in nilNodes)
{
XmlAttribute nvAttr = doc.CreateAttribute("NV");
nvAttr.Value = "7701003";
node.Attributes.Append(nvAttr);
}
doc.Save("somepath.xml");
The upper answer makes totally sense, but since these classes are auto-generated I will do it my way with the post processing, cause if the provider changes the XSD schema, my solution doesn't need any extra work. Thanks anyway.
There are various types, in a special case which can be configured in different ways. How to serialize them?
[Serializable]
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
object _schemaVersion;
[XmlElement("SchemaVersion")]
public object SchemaVersion
{
get { return _schemaVersion; }
set { _schemaVersion = value; }
}
List<object> _test;
[XmlElement("Test")]
public List<object> Test
{
get { return _test; }
set { _test = value; }
}
public RootXml()
{
}
}
I.e. root can include different objects, and they have to be serialized...
I have a xml-format approximately of such
look:
<?xml version="1.0" encoding="windows-1251"?>
<RootXml>
<SchemaVersion Number="" />
<Report Code="">
<Period Code="" Date="">
<Source ClassCode="" Code="">
<Form Code="">
<Column Num="1" Name="" />
<Column Num="2" Name="" />
<Column Num="3" Name="" />
<Document>
<Data code="11" />
<Data code="12">
<Px Num="1" Value="1" />
<Px Num="2" Value="1" />
<Px Num="4" Value="2" />
<Px Num="5" Value="2" />
</Data>
<Data code="13" />
</Document>
</Form>
</Source>
</Period>
</Report>
</RootXml>
In which some elements can change a little (Document, Document with tags, Document with the status, etc.),
included in others (for example, report incl. in scheme) ... and do not know how to change in the future.
I want to construct a set of "formats" which will also have various components, to be substituted...
Maybe for this purpose you shouldn't use serialization, and to define
set of attributes, and a reflection to process objects and to form xml (approximately just as XmlSerializer)???
You are trying to serialize and deserialize data with polymorphic fields. You have a few options here:
If you know in advance all possible types that might be encountered in a polymorphic field, you can use attributes to tell XmlSerializer how to serialize and deserialize each type. In particular, for a polymorphic field, apply [XmlElement("DerivedElementName", Type = typeof(DerivedElementType))] for every derived type that might be encountered.
For instance, simplifying your RootXml class somewhat, the following allows for two different types of report to be serialized:
[XmlRoot("Report", Namespace = "")]
public class Report
{
[XmlAttribute]
public string Code { get; set; }
[XmlElement]
public decimal TotalCost { get; set; }
}
[XmlRoot("DifferentReport", Namespace = "fuuuu")]
public class DifferentReport
{
public DifferentReport() { }
public DifferentReport(string code, string value)
{
this.Code = code;
this.Value = value;
}
[XmlAttribute]
public string Code { get; set; }
[XmlText]
public string Value { get; set; }
}
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
public RootXml() { }
object _test;
[XmlElement("Report", Type=typeof(Report))]
[XmlElement("DifferentReport", Type = typeof(DifferentReport))]
public object Data
{
get { return _test; }
set { _test = value; }
}
}
And then later, both of the following can be serialized and deserialized:
var root1 = new RootXml { Data = new Report { Code = "a code", TotalCost = (decimal)101.01 } };
var root2 = new RootXml { Data = new DifferentReport { Code = "a different code", Value = "This is the value of the report" } };
Note that you can use the same technique with polymorphic lists, in which case the serializer will expect sequences of elements with the specified names:
[XmlRoot("RootXml", Namespace = "")]
public class RootXml
{
public RootXml() { }
List<object> _test;
[XmlElement("Report", Type=typeof(Report))]
[XmlElement("DifferentReport", Type = typeof(DifferentReport))]
public List<object> Data
{
get { return _test; }
set { _test = value; }
}
}
If the XML could be anything and you don't know what it might contain (because you must deserialize XML from the future versions and reserialize it without data loss, for example) you may need to load your XML into an XDocument then manually search for data using Linq-to-XML. For information on how to do this, see here: Basic Queries (LINQ to XML).
You could adopt a hybrid approach where you load the XML into an XDocument, then deserialize and serialize familiar portions with XmlSerializer, using the following extension methods:
public static class XObjectExtensions
{
public static T Deserialize<T>(this XContainer element)
{
return element.Deserialize<T>(new XmlSerializer(typeof(T)));
}
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
object result = serializer.Deserialize(reader);
if (result is T)
return (T)result;
}
return default(T);
}
public static XElement Serialize<T>(this T obj, bool omitStandardNamespaces = true)
{
return obj.Serialize(new XmlSerializer(obj.GetType()), omitStandardNamespaces);
}
public static XElement Serialize<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces = true)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
(ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
serializer.Serialize(writer, obj, ns);
}
return doc.Root;
}
}
Then use them to pick out and deserialize known portions of your XML as follows:
var doc = XDocument.Parse(xml);
var reportElement = doc.Root.Element("Report");
if (reportElement != null)
{
var report1 = doc.Root.Element("Report").Deserialize<Report>();
// Do something with the report.
// Create a different report
var differentReport = new DifferentReport { Code = report1.Code + " some more code", Value = "This is the value of the report" };
var differentElement = differentReport.Serialize();
reportElement.AddAfterSelf(differentElement);
reportElement.Remove();
}
OK, given that you are using c# 2.0, you can load your Xml into an XmlDocument and use it as described here: Process XML Data Using the DOM Model. This is a precursor API to Linq-to-XML and is somewhat harder to work with -- but nevertheless totally functional.
You can also adopt the hybrid approach and use XmlSerializer to deserialize and re-serialize known chunks of an XmlDocument. Here are some extension methods for this purpose -- but since you're using c# 2.0, you must remove the this keyword:
public static class XmlNodeExtensions
{
public static XmlElement SerializeToXmlElement<T>(this T o, XmlElement parent)
{
return SerializeToXmlElement(o, parent, new XmlSerializer(o.GetType()));
}
public static XmlElement SerializeToXmlElement<T>(this T o, XmlElement parent, XmlSerializer serializer)
{
int oldCount = parent.ChildNodes.Count;
XPathNavigator navigator = parent.CreateNavigator();
using (XmlWriter writer = navigator.AppendChild())
{
writer.WriteComment(""); // Kludge suggested here: https://social.msdn.microsoft.com/Forums/en-US/9ff20a3c-913d-4c6f-a18a-c10040290862/how-to-xmlserialize-directly-into-xmldocument?forum=asmxandxml
serializer.Serialize(writer, o);
}
XmlElement returnedElement = null;
for (int i = parent.ChildNodes.Count - 1; i >= oldCount; i--)
{
XmlComment comment = parent.ChildNodes[i] as XmlComment;
if (comment != null)
{
parent.RemoveChild(comment);
}
else
{
returnedElement = (parent.ChildNodes[i] as XmlElement) ?? returnedElement;
}
}
return returnedElement;
}
public static XmlDocument SerializeToXmlDocument<T>(this T o)
{
return SerializeToXmlDocument(o, new XmlSerializer(o.GetType()));
}
public static XmlDocument SerializeToXmlDocument<T>(this T o, XmlSerializer serializer)
{
XmlDocument doc = new XmlDocument();
using (XmlWriter writer = doc.CreateNavigator().AppendChild())
serializer.Serialize(writer, o);
return doc;
}
public static T Deserialize<T>(this XmlElement element)
{
return Deserialize<T>(element, new XmlSerializer(typeof(T)));
}
public static T Deserialize<T>(this XmlElement element, XmlSerializer serializer)
{
using (var reader = new XmlNodeReader(element))
return (T)serializer.Deserialize(reader);
}
}
Given those methods, you can do things like:
// Load the document from XML
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
// Find all nodes with name "Report"
foreach (XmlElement reportNode in doc.SelectNodes("/RootXml/Report"))
{
// Deserialize as a Report
Report report = XmlNodeExtensions.Deserialize<Report>(reportNode);
// Do something with it
// Create a new Report, based on the original report.
DifferentReport differentReport = new DifferentReport(report.Code + " some more code", "This is the value of the report"); ;
// Add the new report to the children of RootXml
XmlElement newNode = XmlNodeExtensions.SerializeToXmlElement(differentReport, (XmlElement)reportNode.ParentNode);
}
As you can see this is quite similar to what is possible with Linq-to-XML.
I have the following XML structure (edited for brevity) which I have no control over.
<GetVehicles>
<ApplicationArea>
<Sender>
<Blah></Blah>
</Sender>
</ApplicationArea>
<DataArea>
<Error>
<Blah></Blah>
</Error>
<Vehicles>
<Vehicle>
<Colour>Blue</Colour>
<NumOfDoors>3</NumOfDoors>
<BodyStyle>Hatchback</BodyStyle>
<Vehicle>
</Vehicles>
</DataArea>
</GetVehicles>
I have the following Class:
[XmlRoot("GetVehicles"), XmlType("Vehicle")]
public class Vehicle
{
public string Colour { get; set; }
public string NumOfDoors { get; set; }
public string BodyStyle { get; set; }
}
I want to be able to deserialize the XML into a single instance of this Vehicle class. 99% of the time, the XML should only return a single 'Vehicle' element. I'm not yet dealing with it yet if it contains multiple 'Vehicle' elements inside the 'Vehicles' element.
Unfortunately, the XML data isn't currently being mapped to my class properties; they are being left blank upon calling my Deserialize method.
For completeness, here is my Deserialize method:
private static T Deserialize<T>(string data) where T : class, new()
{
if (string.IsNullOrEmpty(data))
return null;
var ser = new XmlSerializer(typeof(T));
using (var sr = new StringReader(data))
{
return (T)ser.Deserialize(sr);
}
}
I don't care about the other more parent elements such as 'ApplicationArea', 'Error' etc. I am only interesting in extracting the data within the 'Vehicle' element. How do I get it to only deserialize this data from the XML?
Building on Ilya's answer:
This can be optimized slightly, since there is already a loaded node: there is no need to pass the xml down to a string (using vehicle.ToString()), only to cause the serializer to have to re-parse it (using a StringReader).
Instead, we can created a reader directly using XNode.CreateReader:
private static T Deserialize<T>(XNode data) where T : class, new()
{
if (data == null)
return null;
var ser = new XmlSerializer(typeof(T));
return (T)ser.Deserialize(data.CreateReader());
}
Or if data is a XmlNode, use a XmlNodeReader:
private static T Deserialize<T>(XmlNode data) where T : class, new()
{
if (data == null)
return null;
var ser = new XmlSerializer(typeof(T));
using (var xmlNodeReader = new XmlNodeReader(data))
{
return (T)ser.Deserialize(xmlNodeReader);
}
}
We can then use:
var vehicle = XDocument.Parse(xml)
.Descendants("Vehicle")
.First();
Vehicle v = Deserialize<Vehicle>(vehicle);
I'm not aware of the full context of your problem, so this solution might not fit into your domain. But one solution is to define your model as:
[XmlRoot("Vehicle")] //<-- optional
public class Vehicle
{
public string Colour { get; set; }
public string NumOfDoors { get; set; }
public string BodyStyle { get; set; }
}
and pass specific node into Deserialize method using LINQ to XML:
var vehicle = XDocument.Parse(xml)
.Descendants("Vehicle")
.First();
Vehicle v = Deserialize<Vehicle>(vehicle.ToString());
//display contents of v
Console.WriteLine(v.BodyStyle); //prints Hatchback
Console.WriteLine(v.Colour); //prints Blue
Console.WriteLine(v.NumOfDoors); //prints 3