I have a xml schema in the form of
<?xml version="1.0" encoding="UTF-8"?>
<project ref="edward" name="Edward(A)">
<desc/>
<Zones>
<Zone ref="1" name="Zone1"/>
<Zone ref="2" name="Zone2"/>
<Zone ref="3" name="Zone3"/>
<Zone ref="4" name="Zone4"/>
</Zones>
</project>
I am trying to extract all the Zones value using Xml to Linq (not an expert)
I tried
string xmlString = System.IO.File.ReadAllText("..\\..\\..\\v1.xml");
XDocument xdoc = new XDocument();
xdoc=XDocument.Parse(xmlString);
var ele = xdoc.Descendants("Zones")
.Select(x => (string)x.Element("name"))
.FirstOrDefault();
// this is null
var result = xdoc.Element("project").Descendants("Zones").Descendants("Zone");
foreach (var item in result)
{
Console.WriteLine(item.name); //what should be here
}
You could add the items to a list or using a Dictionary<int, string> may look something like...
Dictionary<int, string> Zones = new Dictionary<int, string>();
foreach (var item in result) {
int.TryParse(item.Attribute("ref").Value, out int value);
Zones.Add(value, item.Attribute("name").Value);
Console.WriteLine(item.Attribute("name").Value);
Console.WriteLine(item.Attribute("ref").Value);
}
Another approach using two simple Classes…
Looking closer, I am betting using a couple of classes and the System.Xml.Serialization; library may make this easier. Given the XML file. One Class appears obvious… called a Zone object with the int and string properties. Then another Class we could call ZoneProject.
The ZoneProject Class would have three (3) properties. In this case two string properties called Ref and Name, and a List<Zone> of Zone objects called Zones. The bare minimum of these two classes may look something like…
The Zone Class
public class Zone {
[XmlAttribute("ref")]
public int Ref { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
}
The ZoneProject Class
[XmlRoot("project")]
public class ZoneProject {
[XmlAttribute("ref")]
public string Ref { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
[XmlArray("Zones")]
public List<Zone> Zones { get; set; }
}
To help “deserialize” the XML we added additional qualifiers to help mate the XML element/attributes to the particular property of each class. The XmlRoot is the base class ZoneProject with the XmlAttributes and the collection of Zone objects using the XmlArray qualifier. This property will be a List<Zone> collection. The same follows for the Zone Class.
With this set up, we can use the System.Xml.Serialization; library and deserialize the XML file into a single ZoneProject object. Something like…
string filePath = #"PathToYourXML_File\file.xml";
XmlSerializer serializer = new XmlSerializer(typeof(ZoneProject));
ZoneProject TheZoneProject;
using (FileStream fs = File.OpenRead(filePath)) {
TheZoneProject = (ZoneProject)serializer.Deserialize(fs);
}
It should be noted, that the answer from #Enyra on the SO question How to read XML file into List<>? …was helpful in the answer above.
I hope this makes sense.
Please check your xml file path. I think that , problem here.
string xmlString = System.IO.File.ReadAllText(dicorrectPath);
You can collect you zones attributes with Linq:
var result = xdoc.Element("project").Descendants("Zones").Descendants("Zone");
var zonesAtributes = result.Select(x =>
new
{
name = x.Attribute("name").Value,
#ref = x.Attribute("ref").Value
}).ToArray();
Related
I do have a problem with serializing a ArrayList. Most propably I use wrong XML Attributes for it since I when I changed them it would not even serialize it and got errors like 'The type may not be used in this context.'
I need to use a non generic ArrayList. On adding [XmlArray("LineDetails")] made this code to run but the output is not correct, it should give me the LineDetails structure. Any idea how to fix this?
This is a part of a whole xml like Document > Header > LineCollection > LineDeatails.
The problem is only with this details if I use a standard string field it is ok but the range of the colletion if changing with every document.
[XmlType(TypeName = "LineCollection")]
public class LineCollection
{
public String LineCount{ get; set; }
// [XmlElement(ElementName = "LineDetails")]
[XmlArray("LineDetails")]
public ArrayList LineDetails{ get; set; }
}
public class LineDetails: ArrayList
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => new UTF8Encoding(false);
}
public string Serialize()
{
// var xmlserializer = new XmlSerializer(this.GetType());
var xmlserializer = new XmlSerializer(this.GetType(), new Type[] { typeof(LineDetails) });
var Utf8StringWriter = new Utf8StringWriter();
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, string.Empty);
using (var writer = XmlWriter.Create(Utf8StringWriter))
{
xmlserializer.Serialize(writer, this, xns);
return Utf8StringWriter.ToString();
}
}
And the incorrect output of this...
<LineColletion>
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
</LineDetails>
</LineColletion>
it should be like this
<LineColletion>
<LineDetails>
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</LineDetails>
<LineDetails>
<LineNum>2</LineNum>
<ItemId>Item_232100000</ItemId>
<ItemName>TheItemName0</ItemName>
</LineDetails>
<LineDetails>
<LineNum>3</LineNum>
<ItemId>Item_23217777</ItemId>
<ItemName>TheItemName7</ItemName>
</LineDetails>
</LineColletion>
Now the wrong xml looks like this...
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>2</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
</LineDetails>
You may generate the required XML by modifying your data model as follows:
[XmlType(TypeName = "LineColletion")] // Fixed: TypeName. But do you want LineColletion (misspelled) or LineCollection (correctly spelled)? Your XML shows LineColletion but your code used LineCollection.
public class LineCollection
{
public String LineCount{ get; set; }
[XmlElement("LineDetails", typeof(LineDetails))] // Fixed -- specify type(s) of items in the ArrayList.
public ArrayList LineDetails{ get; set; }
}
public class LineDetails // Fixed: removed inheritance from ArrayList.
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
Notes:
Your model makes LineDetails inherit from ArrayList. XmlSerializer will never serialize collection properties, it will only serialize collection items. In order to serialize it correctly, I removed the inheritance since you don't seem to be using it anyway.
If you really need LineDetails to inherit from ArrayList, you will need to implement IXmlSerializable or replace it collection with a DTO.
Implementing IXmlSerializable is tedious and error-prone. I don't recommend it.
Your LineDetails collection is serialized without an outer wrapper element. To make the serializer do this, apply XmlElementAttribute to the property.
ArrayList is an untyped collection, so you must inform XmlSerializer of the possible types it might contain. You have two options:
Assigning a specific type to a specific element name by setting XmlElementAttribute.Type (or XmlArrayItemAttribute.Type), OR
Adding xsi:type attributes by informing the serializer of additional included types. You are doing this by passing them into the constructor, which is why you are seeing the p5:type="LineDetails" attribute.
Since you don't want the attributes, you need to set the element name by setting the type like so:
[XmlElement("LineDetails", typeof(LineDetails))]
The XML element corresponding to your LineCollection is named <LineColletion>. Note that the spelling is inconsistent. You will need to set [XmlType(TypeName = "LineColletion")] to the name you actually want.
Demo fiddle here.
I have an entity as below
public class Vehicle{
public int VehicleId {get;set;};
public string Make {get;set;};
public string Model{get;set;}
}
I wanted to serialize as below
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common" >HXV</Make>
<Model AppliesTo="C2">34-34</Model>
</Vehicle>
I have around 100 properties like this in Vehicle class, for each vehicle property I wanted to attach a metadata ApplieTo which will be helpful to downstream systems. AppliesTo attribute is static and its value is defined at the design time. Now How can I attach AppliesTo metadata to each property and inturn get serialized to XML?
You can use XElement from System.Xml.Linq to achieve this. As your data is static you can assign them easily. Sample code below -
XElement data= new XElement("Vehicle",
new XElement("VehicleId", new XAttribute("AppliesTo", "C1"),"1244"),
new XElement("Make", new XAttribute("AppliesTo", "Common"), "HXV"),
new XElement("Model", new XAttribute("AppliesTo", "C2"), "34 - 34")
);
//OUTPUT
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common">HXV</Make>
<Model AppliesTo="C2">34 - 34</Model>
</Vehicle>
If you are not interested in System.Xml.Linq then you have another option of XmlSerializer class. For that you need yo define separate classes for each property of vehicle. Below is the sample code and you can extend the same for Make and Model -
[XmlRoot(ElementName = "VehicleId")]
public class VehicleId
{
[XmlAttribute(AttributeName = "AppliesTo")]
public string AppliesTo { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "Vehicle")]
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")]
public VehicleId VehicleId { get; set; }
//Add other properties here
}
Then create test data and use XmlSerializer class to construct XML -
Vehicle vehicle = new Vehicle
{
VehicleId = new VehicleId
{
Text = "1244",
AppliesTo = "C1",
}
};
XmlSerializer testData = new XmlSerializer(typeof(Vehicle));
var xml = "";
using (var sww = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sww))
{
testData.Serialize(writer, vehicle);
xml = sww.ToString(); // XML
}
}
It is not easy or ideal to use the default .NET XML serializer (System.Xml.Serialization.XmlSerializer) in the way you want, but it's possible. This answer shows how to create a class structure to hold both your main data and the metadata, then use XmlAttributeAttribute to mark a property so it gets serialized as an XML attribute.
Assumptions:
There are a number of unknowns about your intended implementation, such as:
The XML serializer you want to use (default for .NET?)
The mechanism to inject 'AppliesTo' (attribute?)
Do you care about deserialization?
This answer assumes the default .NET serializer, that deserialization matters, and that you don't care about the exact method of injecting your metadata.
Key concepts:
A generic class to hold both our main property value and the metadata (see PropertyWithAppliesTo<T>)
Using XmlAttributeAttribute on the generic class' metadata, so it is written as an XML attribute on the parent property
Using XmlTextAttribute on the generic class' main data, so it is written as the Xml text of the parent property (and not as a sub-property)
Including two properties on the main type being serialized (in this case Vehicle) for every value you want serialized: one of the new generic type that gets serialized with metadata, and one of the original type marked with XmlIgnoreAttribute that provides 'expected' access to the property's value
Using the XmlElementAttribute to change the name of the serialized property (so it matches the expected name)
Code:
using System;
using System.IO;
using System.Xml.Serialization;
namespace SomeNamespace
{
public class Program
{
static void Main()
{
var serializer = new XmlSerializer(typeof(Vehicle));
string s;
var vehicle = new Vehicle { VehicleId = 1244 };
//serialize
using (var writer = new StringWriter())
{
serializer.Serialize(writer, vehicle);
s = writer.ToString();
Console.WriteLine(s);
}
// edit the serialized string to test deserialization
s = s.Replace("Common", "C1");
//deserialize
using (var reader = new StringReader(s))
{
vehicle = (Vehicle)serializer.Deserialize(reader);
Console.WriteLine($"AppliesTo attribute for VehicleId: {vehicle.VehicleIdMeta.AppliesTo}");
}
}
}
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")] // renames to remove the 'Meta' string
public PropertyWithAppliesTo<int> VehicleIdMeta { get; set; } = new PropertyWithAppliesTo<int>("Common");
[XmlIgnore] // this value isn't serialized, but the property here for easy syntax
public int VehicleId
{
get { return VehicleIdMeta.Value; }
set { VehicleIdMeta.Value = value; }
}
}
public class PropertyWithAppliesTo<T>
{
[XmlAttribute] // tells serializer this should be an attribute on this element, not a property
public string AppliesTo { get; set; } = string.Empty;
[XmlText] // tells serializer to not write this as a property, but as the main XML text
public T Value { get; set; } = default;
public PropertyWithAppliesTo() : this(string.Empty) { }
public PropertyWithAppliesTo(string appliesTo) : this(appliesTo, default) { }
public PropertyWithAppliesTo(string appliesTo, T initialValue)
{
AppliesTo = appliesTo;
Value = initialValue;
}
}
}
When run, the string s will look like:
<?xml version=\"1.0\" encoding=\"utf-16\"?>
<Vehicle xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
<VehicleId AppliesTo="Common">1244</VehicleId>
</Vehicle>
Other Notes:
You can see how to add more properties to Vehicle: add a property of type PropertyWithAppliesTo<T> marked with XmlElement to give it the name you want, and then a property of type T marked with XmlIgnore that wraps around the Value you want.
You can control the value of AppliesTo by changing the input to the constructor of PropertyWithAppliesTo<T> and giving it a different metadata string.
If you don't want consumers of your library to see the 'meta' properties in IntelliSense, you can use the EditorBrowsableAttribute. It won't hide things from you when using the source and a project reference; it's only hidden when referencing the compiled dll.
This is admittedly an annoying way to add properties to a class. But if you want to use the default .NET XML serializer, this is a way to achieve the XML you want.
I try to read an xml file with almost 8 leves of child nodes but without succsses.
I try using LINQ, SelectNode, SelectSingleNode, try to use a dumb foreach over XMLNodeList.
In my last intent I use this dumb foreach over the XMLNodeList to try to catch the text or value of some nodes.
These nodes are in diferents levels of deep but I can get onle the first element in sequesnse but the rest only repeat the firs value.
This is part of my code.
XmlDocument xDocXML = new XmlDocument();
xDocXML.Load(file_name);//file_name is a string with the full path of the file
XmlNodeList Article = xDocXML.SelectNodes("/ArticleSet/Article/Document"); //We get the Document of the article
foreach(XmlNode n in Article)
{
spmid = n["ID"].InnerText;
liga = string.Concat(TestString1, spmid);
//Test 1
//stitle = n.SelectSingleNode("//Article/ArticleTitle").InnerText;
//Test 2
//stitle = n["//Article/ArticleTitle"].InnerText;
XmlNode titles = n.SelectSingleNode("//Article/ArticleTitle");
stitle = titles.InnerText;//This line only work once and it repeat in all xmlnodes read
camposcuenta = camposcuenta + 1;
dt_abstractdb.Rows.Add(new Object[] { camposcuenta.ToString(), spmid, stitle, sresum, liga, ligaPDF, ligadoi });
}
Any suggestion over this
Without knowing what your XML looks like, I would recomend creating a class to represent your XML file and then use serialization. With this solution you can have multiple levels and let the Framework handle them.
Check this for example: How to Deserialize XML document
You can also use an external tool to generate your POCO classes, for exemple: https://xmltocsharp.azurewebsites.net/
Example code from the link's solution:
Classes to represent your XML:
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElement("StockNumber")]
public string StockNumber { get; set; }
[System.Xml.Serialization.XmlElement("Make")]
public string Make { get; set; }
[System.Xml.Serialization.XmlElement("Model")]
public string Model { get; set; }
}
[Serializable()]
[System.Xml.Serialization.XmlRoot("CarCollection")]
public class CarCollection
{
[XmlArray("Cars")]
[XmlArrayItem("Car", typeof(Car))]
public Car[] Car { get; set; }
}
Reading code:
CarCollection cars = null;
string path = "cars.xml";
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
StreamReader reader = new StreamReader(path);
cars = (CarCollection)serializer.Deserialize(reader);
reader.Close();
I have XML Serializable class with property Name
[Serializable]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
}
and I want it to be able to deserialize XML file that I have in two ways:
<item>
<name>Name</name>
</item>
and
<item>
<name value="Name" />
</item>
The first works fine but what should I do to be able to deserialize the second also with the same class?
XML Serialization attributes work both with serialization and deserialization. If we'll assume that it might be possible to use attributes for deserializing instance of Item from two different xml structures, then how serialization should work - should it serialize instance name to element value, or to attribute? Or to both? That's why you cannot deserialize two different xml structures into single class. Use two different classes or deserialize it manually without usage of XML Serialization attributes.
I found another way to solve my problem using only one class maybe someone will find this useful
[Serializable]
public class Item
{
[XmlElement("name")]
public NameElement NameElement { get; set; }
}
public class NameElement
{
[XmlAttribute("value")]
public string Value { get; set; }
[XmlText]
public string Text { get; set; }
[XmlIgnore]
public string Name
{
get { return String.IsNullOrEmpty(this.Value) ? this.Text : this.Value; }
set { this.Value = value; }
}
}
Maybe it's not super elegant but it works in both cases and uses the same class.
Since you have mentioned that XML data is coming from external sources, so obviously you don't have control over that.
Therefore you can follow any of the option as below:
Create separate class per XML data structure, because as far I know there is no way to control XML Deserialization when using XmlSerializer
You can use XDocument to read the XML by self, to overcome this limitation.
If going by second idea, I have created small Console Application to demonstrate that.
Main piece of code is as below:
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlData));
XDocument doc = XDocument.Load(xmlStream);
var records = from record in doc.Descendants("item").Descendants()
select new Item(!record.IsEmpty ? record.Value : record.Attribute("value").Value);
Here I'm reading the element using LinqToXml and checking if the element is not empty, i.e. Value is not blank, then use Value otherwise read the value from element's Attribute.
Console application (Complete code):
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace Console.TestApp
{
class Program
{
static string xmltypeFirst = #"<item>
<name>John</name>
</item>";
static string xmltypeSecond = #"<item>
<name value='Smith' />
</item>";
static void Main(string[] args)
{
var data = xmltypeFirst;
var result = Deserialize(data).ToList();
Console.WriteLine("Name: " + result[0].Name);
data = xmltypeSecond;
result = Deserialize(data).ToList();
Console.WriteLine("Name: " + result[0].Name);
Console.WriteLine("Press any to key to exit..");
Console.ReadLine();
}
private static IEnumerable<Item> Deserialize(string xmlData)
{
MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlData));
XDocument doc = XDocument.Load(xmlStream);
var records = from record in doc.Descendants("item").Descendants()
select new Item(!record.IsEmpty ? record.Value : record.Attribute("value").Value);
return records;
}
}
[Serializable]
public class Item
{
public Item(string name)
{
this.Name = name;
}
[XmlElement("name")]
public string Name { get; set; }
}
}
Note: To run this you will need to add reference to System.Xml.Linq.dll in your project.
Reference: here
I have an XML document:
<data>
<elmt1>Element 1</elmt1>
<elmnt2>Element 2</elmnt2>
<elmnt3>Element 3</elmnt3>
</data>
I need to deserialize to an object that serializes to a different root name with everything else remaining the same.
For example:
<dataNew>
<elmt1>Element 1</elmt1>
<elmnt2>Element 2</elmnt2>
<elmnt3>Element 3</elmnt3>
</dataNew>
When serializing, we can always apply XmlRootAttribute to serialize to a different root name but I am not sure how to deserialize to a different XmlRootAttribute. It keeps failing error in document (1,2) pointing to the root attribute.
How can I achieve that?
If it's only the root name you want to change you can specify the root attribute when declaring the XmlSerializer.
XmlSerializer xmlSerializer = new XmlSerializer(typeof(data), new XmlRootAttribute("dataNew"));
XmlRootAttribute was supposed to work
[XmlRoot("dataNew")]
public class MyData()
{
[XmlElement("elmt1")]
public string myElement1{get;set;}
[XmlElement("elmnt2")]
public string myElement2{get;set;}
[XmlElement("elmtn3")]
public string myElement3{get;set;}
}
EDIT: Completed the XML
Did you try using the XmlAttributeOverrides class?
a sample of using XmlAttributeOverrides. If you vote up give one to hjb417 as well
class Program
{
static void Main(string[] args)
{
using (var fs = File.OpenRead("XmlFile1.xml"))
using (var fs2 = File.OpenRead("XmlFile2.xml"))
{
var xSer = new XmlSerializer(typeof(data));
var obj = xSer.Deserialize(fs);
//
var xattribs = new XmlAttributes();
var xroot = new XmlRootAttribute("dataNew");
xattribs.XmlRoot = xroot;
var xoverrides = new XmlAttributeOverrides();
xoverrides.Add(typeof(data), xattribs);
var xSer2 = new XmlSerializer(typeof(data), xoverrides);
var obj2 = xSer2.Deserialize(fs2);
}
}
}
public class data
{
public string elmt1 { get; set; }
public string elmnt2 { get; set; }
public string elmnt3 { get; set; }
}
You can use ExtendedXmlSerializer. This serializer support change root element name and property name.
If you have class like this:
[XmlRoot("dataNew")]
public class Data
{
[XmlElement("elmt1")]
public string Element1 { get; set; }
[XmlElement("elmnt2")]
public string Element2 { get; set; }
[XmlElement("elmtn3")]
public string Element3 { get; set; }
}
You can serialize it:
ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var obj = new Data
{
Element1 = "A",
Element2 = "B",
Element3 = "C",
};
var xml = serializer.Serialize(obj);
Your xml will look like:
<?xml version="1.0" encoding="utf-8"?>
<dataNew type="Models.Example">
<elmt1>A</elmt1>
<elmnt2>B</elmnt2>
<elmtn3>C</elmtn3>
</dataNew>
ExtendedXmlSerializer has many other useful features:
Deserialization xml from standard XMLSerializer
Serialization class with property interface
Serialization circular reference and reference Id
Deserialization of old version of xml
Property encryption
Custom serializer
ExtendedXmlSerializer supports .net 4.5 and .net Core. You can integrate it with WebApi and AspCore.
You might have to implement ISerializable and change the root element in GetObjectData().