I'm using the XmlReader and XmlWriter classes along with the XmlSerializer class to serialize and deserialize some XML models. Sadly, the format of the XML is out of my hands, and one of the files expects an invalid format, something like this:
<?xml version="1.0"?>
<some_element>...</some_element>
<some_element>...</some_element>
<some_element>...</some_element>
Basically, it doesn't have a singleton root node. Can I make the serializer somehow read and reproduce such a format?
Note: I'd like to stick to the standard interface (IXmlSerializable), so I don't have to differentiate between this and other models.
Edit:
I've tried to implement IXmlSerializable explicitly for the type:
public class TheInvalidModel : IXmlSerializable
{
[XmlElement("some_element")]
public List<SomeElement> Elements { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
// TODO
}
public void WriteXml(XmlWriter writer)
{
var ser = new XmlSerializer(typeof(SomeElement));
foreach (var e in Elements)
{
ser .Serialize(writer, e);
}
}
}
Sadly, this automatically writes out a root element.
Edit 2:
With this sample:
var model = new TheInvalidModel
{
Elements = new List<SomeElement>
{
new SomeElement { },
new SomeElement { },
new SomeElement { },
}
};
var serializer = new XmlSerializer(typeof(TheInvalidModel));
var tw = new StringWriter();
serializer.Serialize(tw, model);
Console.WriteLine(tw.ToString());
I get the following output:
<?xml version="1.0" encoding="utf-16"?>
<TheInvalidModel>
<SomeElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
<SomeElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
<SomeElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
</TheInvalidModel>
But what I'd need instead is:
<?xml version="1.0" encoding="utf-16"?>
<SomeElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
<SomeElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
<SomeElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
You could write non-conformant xml code with a more manual approach;
https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlwritersettings.conformancelevel?view=netcore-3.1
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.CloseOutput = false;
// Create the XmlWriter object and write some content.
MemoryStream strm = new MemoryStream();
XmlWriter writer = XmlWriter.Create(strm, settings);
writer.WriteElementString("orderID", "1-456-ab");
writer.WriteElementString("orderID", "2-36-00a");
writer.Flush();
writer.Close();
// Do additional processing on the stream.
With this you could loop your SomeElements list and output the xml.
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string INPUT_FILENAME = #"c:\temp\test.xml";
const string OUTPUT_FILENAME = #"c:\temp\test1.xml";
static void Main(string[] args)
{
StringBuilder builder = new StringBuilder(File.ReadAllText(INPUT_FILENAME));
StringWriter tw = new StringWriter(builder);
//fill string writer with existing code
//I just used input file for testing
XElement model = XElement.Parse(tw.ToString());
string ident = "<?xml version=\"1.0\" encoding=\"utf-16\"?>";
StreamWriter writer = new StreamWriter(OUTPUT_FILENAME);
writer.WriteLine(ident);
foreach(XElement someElement in model.Descendants("SomeElement"))
{
writer.WriteLine(someElement);
}
writer.Flush();
writer.Close();
}
}
}
Related
I'm using Winforms and I get the following error when I press the load button after having hit the save button:
InvalidOperationException: was not expected.
Here is my code:
private void save_Click(object sender, EventArgs e)
{
// if you don't specify it, then it saves in the bin folder of the program
FileStream fs = new FileStream(#"C:\Users\Owner\Desktop\Student2.Xml", FileMode.Append, FileAccess.Write); //#"C:\Users\Owner\Desktop\Student.Xml"
StudentClass sc = new StudentClass();
sc.Name = textBox1.Text;
sc.Class = int.Parse(textBox2.Text);
ls.Add(sc);
StudentSerializer.Serialize(fs, ls);
DicSerializer.Serialize(fs,
dict.Select(kv => new DicItem() { id = kv.Key, value = kv.Value }).ToArray());
fs.Close();
}
private void load_Click(object sender, EventArgs e)
{
FileStream fs = new FileStream(#"C:\Users\Owner\Desktop\Student2.Xml", FileMode.Open, FileAccess.Read);
ls = (List<StudentClass>)StudentSerializer.Deserialize(fs);
var orgDict = ((DicItem[])DicSerializer.Deserialize(fs))
.ToDictionary(i => i.id, i => i.value);
foreach (var item in orgDict)
Debug.WriteLine(item.Key + " and " + item.Value);
fs.Close();
}
My dictionary already has two items in it. And my student class has two items as well from the textboxes. Also note, my xml file gets wiped clean after that error. Any help is appreciated thanks.
Also, this is what my xml looks like right after hitting the save button:
<?xml version="1.0"?>
<ArrayOfStudentClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<StudentClass>
<Name>wewewe</Name>
<Class>2222</Class>
</StudentClass>
</ArrayOfStudentClass><?xml version="1.0"?>
<DicItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DicItem id="1" value="one" />
<DicItem id="2" value="two" />
</DicItem>
The complaint is because of <?xml version="1.0"?> right before the first DictItem tag. Such tag AFAIK should appear once only in the XML file.
You should post the Deserializer code for each class for more investigation.
If you remove the second declaration than following code will work
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)
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
XmlReader reader = XmlReader.Create(FILENAME, settings);
reader.ReadToFollowing("ArrayOfStudentClass");
XElement ArrayOfStudentClass = (XElement)XElement.ReadFrom(reader);
reader.ReadToFollowing("DicItem");
XElement DicItem = (XElement)XElement.ReadFrom(reader);
}
}
}
I've created a program to retrieve data from a database and convert it to XML. It returns a list of 24 objects that I created:
<?xml version = "1.0" encoding = "utf-16"
<ArrayOfData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Data>
<interval>01</interval>
<num>12345</num>
<id>ABC123 </id>
<type>LETTER</type>
<party>1</party>
<amount>-10</amount>
</Data>
<Data>
<interval>02</interval>
<num>12345</num>
<id>ABC123 </id>
<type>LETTER</type>
<party>1</party>
<amount>-11</amount>
</Data>
<Data>
<interval>03</interval>
<num>12345</num>
<id>ABC123 </id>
<type>LETTER</type>
<party>1</party>
<amount>-12</amount>
</Data>
<Data>
<interval>04</interval>
<num>12345</num>
<id>ABC123 </id>
<type>LETTER</type>
<party>1</party>
<amount>-13</amount>
</Data>
Basically the data goes on with incrementing intervals by 1 up to 24.
What I am looking for is to look for what is duplicate and just concatenate them into one child, and then to look for what differs and create a different element for it but keep them everything in one child. How can I write the XML to do this? Example:
<Data>
<num>12345</num>
<id>ABC123 </id>
<type>LETTER</type>
<party>1</party>
<details>
<interval>01</interval>
<amount>-10</amount>
</details>
<details>
<interval>02</interval>
<amount>-11</amount>
</details>
<details>
<interval>03</interval>
<amount>-12</amount>
</details>
<details>
<interval>04</interval>
<amount>-13</amount>
</details>
</Data>
I want to create a function that returns this XML. The parameters in my current function now are just there to get the data using those parameters. Here's how I generate the XML now:
public IActionResult CreateXML(int num, int id)
{
string xml = "";
var temp = (from x in Context.InternalData.AsNoTracking() where (x.num == num && x.id == id) select x).ToList();
using (var stringwrite = new Utf8StringWriter())
{
var serializer = new XmlSerializer(temp.GetType());
serializer.Serialize(stringwrite, temp);
xml = stringwrite.ToString();
StringReader reader = new StringReader(stringwriter.ToString());
reader.ReadLine(); //skips the UTF-16 header
XDocument doc = XDocument.Load(reader);
XElement arrayOfData = doc.Root;
List<XElement> newData = new List<XElement>();
var parties = doc.Descendants("Data")
.GroupBy(x => (string)x.Element("id"))
.ToList();
foreach (var party in parties)
{
XElement template = party.First();
List<XElement> details = party.Select(x => new XElement("details", new object[] {
new XElement("interval", (string)x.Element("interval")),
new XElement("amount", (string)x.Element("amount"))
})).ToList();
template.Element("interval").Remove();
template.Element("amount").Remove();
template.Add(details);
newData.Add(template);
}
arrayOfData.ReplaceAll(newData);
return Ok(arrayOfData);
}
}
I am new to this and have never worked with generating XML before. Any help would be greatly appreciated!
Try xml linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
StringWriter stringwriter = new StringWriter();
stringwriter.Write(xml);
StringReader reader = new StringReader(stringwriter.ToString());
reader.ReadLine(); //skips the UTF-16 header
XDocument doc = XDocument.Load(reader);
XElement arrayOfData = doc.Root;
List<XElement> newData = new List<XElement>();
var parties = doc.Descendants("Data")
.GroupBy(x => (string)x.Element("id"))
.ToList();
foreach (var party in parties)
{
XElement template = party.First();
List<XElement> details = party.Select(x => new XElement("details", new object[] {
new XElement("interval", (string)x.Element("interval")),
new XElement("amount", (string)x.Element("amount"))
})).ToList();
template.Element("interval").Remove();
template.Element("amount").Remove();
template.Add(details);
newData.Add(template);
}
arrayOfData.ReplaceAll(newData);
}
}
}
I have this class
public class EnvioDTE
{
[XmlAttribute]
public string version { get; set; }
public EnvioDTE()
{
this.version = "1.0";
}
}
I am serializing it to XML with the following code:
EnvioDTE envioDTE = new EnvioDTE();
string xml = "";
var serializer = new XmlSerializer(typeof(EnvioDTE));
var settings = new XmlWriterSettings
{
Indent = true,
NewLineChars = "\n",
OmitXmlDeclaration = false,
Encoding = Encoding.GetEncoding("ISO-8859-1")
};
using (var stream = new MemoryStream())
{
using (var xmlWriter = XmlWriter.Create(stream, settings))
{
serializer.Serialize(xmlWriter, this);
xml = Encoding.GetEncoding("ISO-8859-1").GetString(stream.ToArray());
}
}
Console.WriteLine(xml);
Which is giving me the following XML:
<?xml version="1.0" encoding="iso-8859-1"?>
<EnvioDTE version="1.0">
</EnvioDTE>
What do I need to add to my code so that I get the following attributes?
<?xml version="1.0" encoding="iso-8859-1"?>
<EnvioDTE xmlns="http://www.sii.cl/SiiDte" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sii.cl/SiiDte EnvioDTE_v10.xsd" version="1.0">
</EnvioDTE>
The order of the attributes is important for me.
I tried by making two changes:
Change #1
[XmlRoot(Namespace = "http://www.sii.cl/SiiDte")] // *** NEW
public class EnvioDTE
{
[XmlAttribute("schemaLocation", Namespace = "http://www.w3.org/2001/XMLSchema-instance")] // *** NEW
public string schemaLocation { get; set; } // *** NEW
[XmlAttribute]
public string version { get; set; }
public EnvioDTE()
{
this.version = "1.0";
}
}
Change #2
EnvioDTE envioDTE = new EnvioDTE();
string xml = "";
var namespaces = new XmlSerializerNamespaces(); // *** NEW
namespaces.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance"); // *** NEW
var serializer = new XmlSerializer(typeof(EnvioDTE));
var settings = new XmlWriterSettings
{
Indent = true,
NewLineChars = "\n",
OmitXmlDeclaration = false,
Encoding = Encoding.GetEncoding("ISO-8859-1")
};
using (var stream = new MemoryStream())
{
using (var xmlWriter = XmlWriter.Create(stream, settings))
{
serializer.Serialize(xmlWriter, this, namespaces); // *** NEW
xml = Encoding.GetEncoding("ISO-8859-1").GetString(stream.ToArray());
}
}
Console.WriteLine(xml);
With those changes I'm getting this:
<?xml version="1.0" encoding="iso-8859-1"?>
<EnvioDTE xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sii.cl/SiiDte EnvioDTE_v10.xsd" version="1.0" xmlns="http://www.sii.cl/SiiDte">
</EnvioDTE>
And that is not the order of attributes I want...
So you are just trying to read out the XML? I would do this:
XmlReader reader = XmlReader.Create(path, settings);
StringBuilder sb = new StringBuilder();
if (reader != null)
{
while (reader.Read())
{
sb.AppendLine(reader.ReadOuterXml());
}
doc = new XmlDocument();
doc.LoadXml(Convert.ToString(sb));
XmlNodeList xmlNodeList;
xmlNodeList = doc.GetElementsByTagName("Whatever your tag name");
for (i = 0; i < xmlNodeList.Count; i++)
{
if (xmlNodeList[i].InnerXml.Length > 0)
{
foo = xmlNodeList[i].InnerXml;
}
}
}
Okay, you are just trying to output? Then do this. I know it's a bit of a hack and there is probably a million ways to do it:
string myXMLstring = "<?xml version=\"1.0\" encoding="iso-8859-1\"?>" +
<EnvioDTE xmlns=\"http://www.sii.cl/SiiDte\"" +
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
"xsi:schemaLocation=\"http://www.sii.cl/SiiDte EnvioDTE_v10.xsd\"" +
"version=\"1.0\">" +
"</EnvioDTE>";
I've escaped the the quotes and built it up as a string. You can even add \r\n anytime you know you need a new line. I hope that helps. Happy coding!
Aim:
- Deserialize data from an xml document and storing it as an array.
- Avoiding manually assigning the data to different strings.
- The xml document will be manually generated
public void DeserializeObject(string filename)
{
try
{
XmlSerializer deserializer = new XmlSerializer(typeof(string[]));
FileStream fs = new FileStream(filename, FileMode.Open);
string[] XmlData = (string[])deserializer.Deserialize(fs);
foreach (string p in XmlData)
{
Console.WriteLine(p);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
The XML document is as follows
<?xml version="1.0" encoding="utf-8"?>
<Mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Products>
<Product>
<software>Seiko</software>
</Product>
<Product>
<hardware>Martina</hardware>
</Product>
</Products>
</Mapping>
Thank you, found this solution
<?xml version="1.0" encoding="utf-8" ?>
<Locations>
<Location Name="Location1" IP="127.0.0.1"></Location>
<Location Name="Location2" IP="127.0.0.1"></Location>
<Location Name="Location3" IP="127.0.0.1"></Location>
<Location Name="Location4" IP="127.0.0.1"></Location>
<Location Name="Location5" IP="127.0.0.1"></Location>
</Locations>
using System.Xml.Linq;
class Program
{
static void Main(string[] args)
{
string[] strarr = GetStringArray("Locations.xml");
foreach (string str in strarr)
{
Console.WriteLine(str);
}
}
public static string[] GetStringArray(string url)
{
XDocument doc = XDocument.Load(url);
var locations = from l in doc.Descendants("Location")
select (string)l.Attribute("Name");
return locations.ToArray();
}
}
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication38
{
class Program
{
static void Main(string[] args)
{
string input =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Mapping xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
"<Products>" +
"<Product>" +
"<software>Seiko</software>" +
"</Product>" +
"<Product>" +
"<hardware>Martina</hardware>" +
"</Product>" +
"</Products>" +
"</Mapping>";
XDocument doc = XDocument.Parse(input);
var results = doc.Descendants("Product").Select(x =>
x.Elements().Select(y => new { type = y.Name, value = (string)y }).ToList()
).SelectMany(z => z).ToList();
var groups = results.GroupBy(x => x.type).ToList();
}
}
}
You need to generate a class from your sample XML.
You can use the xsd.exe to generate a .xsd and from that create a .cs file.
The you need to add this type to your XmlSerializer
See this answer: Generate C# class from XML
XmlSerializer deserializer = new XmlSerializer(typeof(Mapping)); <- Created class type here.
If all you want to do is get the data form the XML-document as an array of strings, you can use XmlDocument to load in the data
XmlDocument doc = new XmlDocument();
doc.Load("file.xml");
Then you can find the nodes you need using an xPath expression:
XmlNodeList nodelist = doc.SelectNodes(...);
This question already has answers here:
How can I make the xmlserializer only serialize plain xml?
(4 answers)
Closed 8 years ago.
This is what I did:
A Serializable class:
[Serializable()]
public class Ticket
{
public string CitationNumber { get; set; }
public decimal Amount { get; set; }
}
Then serialize a model into xml:
var model = cart.Citations
.Select(c => new Ticket(c.Number, c.Amount)).ToList();
var serializer = new XmlSerializer(typeof (List<Ticket>));
var sw = new StringWriter();
serializer.Serialize(sw, model);
return sw.ToString();
The output sw.ToString() is like
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfTicket xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Ticket>
<CitationNumber>00092844</CitationNumber>
<Amount>20</Amount>
</Ticket>
</ArrayOfTicket>
Is there a way to customize the Serialize() output to remove those schema info like: <?xml version="1.0" encoding="utf-16"?> and xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"?
And how can I change the root element ArrayOfTicket into something else?
Do I have control with those output?
You need a few xml tricks...
var serializer = new XmlSerializer(typeof(List<Ticket>));
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
var sw = new StringWriter();
var xmlWriter = XmlWriter.Create(sw, new XmlWriterSettings() { OmitXmlDeclaration = true });
serializer.Serialize(xmlWriter, model, ns);
string xml = sw.ToString();
Output:
<ArrayOfTicket>
<Ticket>
<CitationNumber>a</CitationNumber>
<Amount>1</Amount>
</Ticket>
<Ticket>
<CitationNumber>b</CitationNumber>
<Amount>2</Amount>
</Ticket>
</ArrayOfTicket>
PS: I added Indent = true to XmlWriterSettings to get the above output