My object structure is similar to the simplified code below. Please note that both Countries and Cars need to be classes, I can't use string list/array due to code not included in sample. I want to XML serialize and later deserialize the objects.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace XMLapp
{
public partial class Form1 : Form
{
List<Countries> Country = new List<Countries>();
List<string> cars = new List<string>();
public Form1()
{
InitializeComponent();
cars.Add("Audi");
cars.Add("BMW");
cars.Add("Mercedes");
addCountry("Germany", cars);
cars.Clear();
cars.Add("Ford");
cars.Add("Chevrolet");
cars.Add("Jeep");
addCountry("USA", cars);
TestXmlSerialize();
Console.WriteLine("Generated list");
}
void TestXmlSerialize()
{
XmlSerializer x = new XmlSerializer(Country.GetType());
x.Serialize(Console.Out, Country);
}
void addCountry(string name, List<string> cars)
{
Countries newCountry = new Countries();
newCountry.Name = name;
newCountry.AddCar(cars);
Country.Add(newCountry);
}
}
public class Countries
{
public string Name { get; set; }
List<Cars> car = new List<Cars>();
public void AddCar(List<string> cars)
{
for (int i = 0; i < cars.Count; i++)
{
Cars newCar = new Cars();
newCar.brand = cars[i];
car.Add(newCar);
}
}
class Cars
{
public string brand;
}
}
}
This generates following output:
<?xml version="1.0" encoding="IBM437"?>
<ArrayOfCountries xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Countries>
<Name>Germany</Name>
</Countries>
<Countries>
<Name>USA</Name>
</Countries>
</ArrayOfCountries>
However, I expected something along the lines of
<Countries>
<Name>Germany</Name>
<ArrayOfCars>
<Brand>Audi</Brand>
<Brand>BMW</Brand>
<Brand>Mercedes</Brand>
</ArrayOfCountries>
</Countries>
I can see that the car brands are stored properly in the Locals & Autos window, but how do I include them in the serialization?
XmlSerializer only serializes public fields and properties. You need to make the 'car' field and the class 'Cars' public.
It won't produce the exact xml layout that you posted in your question, but it will let you serialize and deserialize the object.
Related
I have this replicate scenario my XML document below:
<?xml version="1.0" encoding="utf-8"?>
<Home>
<Kitchen>
<Pantry>
<Ingredients>
<Name>Tomato</Name>
<ID>1</Price_ID>
<Name>Tomato</Name>
<Income>Sales</Income> // replace the <Income> element with its value <Sales>
<Cost>Materials</Cost>
<Price>100</Price> // the value for new <Sales> element shall be this <Price> value
</Ingredients>
//.. and thousands more sets of ingredients
</Pantry>
</Kitchen>
</Home>
//.. and thousands more sets of ingredients
And I want to restructure it in the following manner:
<?xml version="1.0" encoding="utf-8"?>
<Home>
<Kitchen>
<Pantry>
<Ingredients>
<Name>Tomato</Name>
<ID>1</ID>
<Name>Tomato</Name>
<Sales>100</Sales> // the <Income> was replaced by its value and the value was taken from the <Price> element that was deleted also
<Cost>Materials</Cost>
</Ingredients>
//.. and thousands more sets of ingredients
</Pantry>
</Kitchen>
</Home>
I'm still trying to figure out how I'm going to do this. I will appreciate any help here.
Using Xml Ling :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication37
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<XElement> ingredients = doc.Descendants("Ingredients").ToList();
foreach (XElement ingredient in ingredients)
{
XElement xIncome = ingredient.Element("Income");
XElement xPrice = ingredient.Element("Price");
xIncome.ReplaceWith(new XElement("Sales", (string)xPrice));
xPrice.Remove();
}
}
}
}
Firstly create a Class for the new Model
public class NewIngredients
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Sales { get; set; }
public string Cost{ get; set; }
}
Presuming the Xml Document is in a file called Kitchen.xml
XElement Kitchen = XElement.Load(#"Kitchen.xml");
then use Linq to Xml to create your new model from the old something like this (Note probably need to check for nulls etc)
var newIngredients = Kitchen.Descendants("Ingredients").Select(x => new NewIngredients
{
Id = int.Parse(x.Element("ID").Value),
Name = x.Element("Name").Value,
Sales = decimal.Parse(x.Element("Price").Value)
Cost = x.Element("Cost").Value
});
Convert back to xml if needed
var serializer = new XmlSerializer(newIngredients.First().GetType());
serializer.Serialize(Console.Out, newIngredients.First()); //Printed to console but could move to file if needed
I am new to working with LINQ to XML, but I can see how it could be helpful to my current problem. I want a method that you pass in an XML Document and an Element value, the method would then return a different Element Value from the same Descendant. For example if I provided a "StationName" I would like to know what "ScannerType" belongs to that "StationName"
Here is my XML
<?xml version="1.0" encoding="utf-8" ?>
<stations>
<station>
<stationName>CH3CTRM1</stationName>
<scannerType>GE LightSpeed VCT</scannerType>
<scannerID>COL02</scannerID>
<siteName>CUMC</siteName>
<inspDose>180</inspDose>
<expDose>100</expDose>
<kernel>STANDARD</kernel>
</station>
<station>
<stationName>CTAWP75515</stationName>
<scannerType>SIEMENS Force</scannerType>
<scannerID>UIA07</scannerID>
<siteName>Iowa</siteName>
<inspDose>careDose</inspDose>
<expDose>careDose</expDose>
<kernel>Qr40 5</kernel>
</station>
<station>
<stationName>JHEB_CT06N_JHOC2</stationName>
<scannerType>SIEMENS Force</scannerType>
<scannerID>JHU04</scannerID>
<siteName>JHU</siteName>
<inspDose>careDose</inspDose>
<expDose>careDose</expDose>
<kernel>Qr40 5</kernel>
</station>
</stations>
Here are the methods that are currently in question
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace ManualPhantomProcessor.XMLParser
{
class SearchXML
{
public string filename = "SiteData.xml";
public string currentDirectory = Directory.GetCurrentDirectory();
public XDocument LoadXML()
{
string siteDataFilePath = Path.Combine(currentDirectory, filename);
XDocument siteData = XDocument.Load(siteDataFilePath);
return siteData;
}
public IEnumerable<string> GetScannerModel(XDocument xmlDocument, string stationName)
{
var query = xmlDocument.Descendants("station")
.Where(s => s.Element("stationName").Value == stationName)
.Select(s => s.Element("scannerType").Value)
.Distinct();
return query;
}
}
}
Here is my Programs.cs file
using ManualPhantomProcessor.XMLParser;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace ManualPhantomProcessor
{
class Program
{
static void Main(string[] args)
{
SearchXML searchXML = new SearchXML();
XDocument siteData = searchXML.LoadXML();
IEnumerable<string> data = searchXML.GetScannerModel(siteData, "CH3CTRM1");
Console.WriteLine(data);
}
}
}
I should be a simple console application, but it seem like no matter what I try I keep getting a null value when I expect the scannerType value from the XML document that corresponds with the station name "CH3CTRM1"
the application doesn't crash but in my console I get the following:
System.Linq.Enumerable+DistinctIterator`1[System.String]
Could explain what I am doing incorrectly?
Your code is good, the problem is here Console.WriteLine(data); the WriteLine take string like a parameter not a list of string. to display the station names use a loop, like the following code :
foreach(string stationName in data)
{
Console.WriteLine(stationName);
}
The documentation of WriteLine
i hope that will help you fix the issue.
What you're seeing is the string form of the IEnumerable<string> returned by GetScannerModel().
There are two possibilities:
Only one scanner model is expected to be found (because a station name is expected
to be unique).
Any number of scanner models can be found.
In the first case, change GetScannerModel() to return a string and have it do soemthing like return query.FirstOrDefault(); (or .First() if you want an exception if no match was found). Your client program then remains unchanged.
In the second case, #Sajid's answer applies - you need to enumerate the IEnumerable in some way, for example through foreach.
Use a dictionary :
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);
List<Station> stations = doc.Descendants("station").Select(x => new Station
{
stationName = (string)x.Element("stationName"),
scannerType = (string)x.Element("scannerType"),
scannerID = (string)x.Element("scannerID"),
siteName = (string)x.Element("siteName"),
inspDose = (string)x.Element("inspDose"),
expDose = (string)x.Element("expDose"),
kernel = (string)x.Element("kernel")
}).ToList();
Dictionary<string, Station> dict = stations
.GroupBy(x => x.stationName, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
public class Station
{
public string stationName {get;set;}
public string scannerType {get;set;}
public string scannerID {get;set;}
public string siteName {get;set;}
public string inspDose {get;set;}
public string expDose {get;set;}
public string kernel { get; set; }
}
}
I am using C# and have a question in relation to de serializing an XML string.
Here is my code to de serialize:
public object XmlDeserializeFromString(string objectData, Type type)
{
var serializer = new XmlSerializer(type);
object result;
using (TextReader reader = new StringReader(objectData))
{
result = serializer.Deserialize(reader);
}
return result;
}
The following XML works with the above function:
<House>
<address>21 My House</address>
<id>1</id>
<owner>Optimation</owner>
</House>
However, the XML from my Web API application does not:
<House xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MVCwithWebAPIApplication.Models">
<address>21 My House</address>
<id>1</id>
<owner>Optimation</owner>
</House>
How can I get the XmlDeserializeFromString function to work with the XML from my Web API application?
The xml return from web api has a default namespace inside. To deserialize this xml into a memory object (defined by a c# class), XmlSerialize has to know whether the class belongs to that namespace or not. This is specified by the property 'Namespace' in RootAttribute attached to that class. If the namespace in xml matches to the declared namespace in c# class, then the xml is successfully deserialized. Otherwise deserialization fails.
More information about xml namespace please see http://www.w3schools.com/xml/xml_namespaces.asp
below is the demo solution for your reference.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
namespace ConsoleApplication8 {
class Program {
static void Main(string[] args) {
var s1 = "<House><address>21 My House</address><id>1</id><owner>Optimation</owner></House>";
var s2 = "<House xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/MVCwithWebAPIApplication.Models\"><address>21 My House</address><id>1</id><owner>Optimation</owner></House>";
House house = (House)XmlDeserializeFromString(s2, typeof(House));
Console.WriteLine(house.ToString());
Console.Read();
}
public static Object XmlDeserializeFromString(string objectData, Type type) {
var serializer = new XmlSerializer(type);
object result;
using (TextReader reader = new StringReader(objectData)) {
result = serializer.Deserialize(reader);
}
return result;
}
}
//this is the only change
[XmlRoot(Namespace="http://schemas.datacontract.org/2004/07/MVCwithWebAPIApplication.Models")]
public class House {
public String address { get; set; }
public String id { get; set; }
public String owner { get; set; }
public override string ToString() {
return String.Format("address: {0} id: {1} owner: {2}", address, id, owner);
}
}
}
I'm not sure how to create a list of objects. I get "Non-invocable member ListObjects.TestObject.UniqueID' cannot be used like a method."
Any insight would be helpful.
The code for the object I'm trying to create a list of:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ListObjects
{
class TestObject
{
public int UniqueID { get; set; }
}
}
Main code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ListObjects
{
class Program
{
static void Main(string[] args)
{
TestObject TestObject = new TestObject();
List<TestObject> ObjectList = new List<TestObject>();
ObjectList.Add(new TestObject().UniqueID(1));
ObjectList.Add(new TestObject().UniqueID(10));
ObjectList.Add(new TestObject().UniqueID(39));
}
}
}
public int UniqueID { get; set; } is not a method its a setter and you use it like a method
do this
ObjectList.Add(new TestObject()
{
UniqueID = 1
});
I cannot rememer the syntax but it is from memory:
repalce this line:
ObjectList.Add(new TestObject().UniqueID(1));
with this line:
ObjectList.Add(new TestObject(){UniqueID = 1});
and do the same for all .Add lines you have.
You are using UniqueId like a method. It is a property and must be assigned. If you know you'll be assigning an ID when creating a TestObject, you should make the constructor support that, and use it accordingly. If not, use ObjectList.Add(new TestObject { UniqueID = 1 }); to assign the value without a constructor.
This is how I would handle the situation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ListObjects
{
class TestObject
{
public int UniqueID { get; set; }
public TestObject(int uniqueId)
{
UniqueID = uniqueId;
}
}
}
Main code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ListObjects
{
class Program
{
static void Main(string[] args)
{
List<TestObject> ObjectList = new List<TestObject>();
ObjectList.Add(new TestObject(1));
ObjectList.Add(new TestObject(10));
ObjectList.Add(new TestObject(39));
}
}
}
There's a couple of things going on here. You don't need an instance of the object to declare the list, just the type.
Also, UniqueID is a property, not a method. You assign values to them (or read values from them), you don't pass in values like a parameter for a method.
You can also initialize the list in one call, like this:
List<TestObject> ObjectList = new List<TestObject>()
{ new TestObject() { UniqueID = 1},
new TestObject() { UniqueID = 10 },
new TestObject() { UniqueID = 39 } };
This will result in a new List<T> where T is of type TestObject, and the list is initialized to 3 instances of TestObject, with each instance initialized with a value for UniqueID.
TestObject TestObject = new TestObject();
Remove this line.
And edit following lines to this syntax
new TestObject { UniqueID = 39 }
I could get this XML file from C# class with XmlSerializer.
<?xml version="1.0" encoding="utf-8"?>
<Component xmlns:spirit="b" xmlns:chrec="a" MovieName="0" BlocksNotCovered="0">
<ClassInfoList>
<chrec:string>hello</chrec:string>
<chrec:string>world</chrec:string>
</ClassInfoList>
<moduleName />
<world>
<x>10</x>
<y>20</y>
</world>
</Component>
How can I add prefix namespaces for chrec and spriti? For example, how can I get this XML file?
<?xml version="1.0" encoding="utf-8"?>
<spirit:Component xmlns:spirit="b" xmlns:chrec="a" MovieName="0" BlocksNotCovered="0">
<spirit:ClassInfoList>
<chrec:string>hello</chrec:string>
<chrec:string>world</chrec:string>
</spirit:ClassInfoList>
<spirit:moduleName />
<chrec:world>
<chrec:x>10</chrec:x>
<chrec:y>20</chrec:y>
</chrec:world>
</spirit:Component>
This is the C# code.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Linq;
namespace Coverage
{
public class Hello
{
public int x;
public int y;
public Hello()
{
x = 10;
y = 20;
}
}
public class Component {
[XmlAttribute("MovieName")]
public int MovieName;
[XmlAttribute("BlocksNotCovered")]
public int BlocksNotCovered;
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces ns;
public List<string> ClassInfoList;
public string moduleName;
public Hello world;
public Component()
{
ClassInfoList = new List<string>() {"hello", "world"};
MovieName = 0;
BlocksNotCovered = 0;
moduleName = "";
world = new Hello();
}
}
class Cov2xml
{
static void Main(string[] args)
{
string xmlFileName = "perf.xml";
Component report = new Component();
TextWriter writeFileStream = new StreamWriter(xmlFileName);
report.ns = new XmlSerializerNamespaces();
report.ns.Add("chrec","a");
report.ns.Add("spirit","b");
var ser = new XmlSerializer(typeof(Component));
ser.Serialize(writeFileStream, report, report.ns);
writeFileStream.Close();
}
}
}
Thanks to the link from competent_tech, I could figure out the way to do it.
How to set the prefix namespace?
You can use XmlRootAttribute, the important thing is that the names space is the namespace, not the namespace name. In the example, it should be "b" not "chrec".
[XmlRootAttribute("Component", Namespace="http://namespace", IsNullable = false)]
public class Component {
How to set the prefix namespace for a specific element?
You can use XmlElement just before the variable.
[XmlElement("xyz", Namespace="http://www.namespace", IsNullable = false)]
int x;
And you'll get this.
<?xml version="1.0" encoding="utf-8"?>
<chrec:Component xmlns:spirit="http:..." MovieName="0" BlocksNotCovered="0" xmlns:chrec="...">
<chrec:ClassInfoList>
<chrec:string>hello</chrec:string>
<chrec:string>world</chrec:string>
</chrec:ClassInfoList>
<chrec:moduleName />
<chrec:world>
<spirit:xyz>10</spirit:xyz>
<chrec:y>20</chrec:y>
</chrec:world>
</chrec:Component>