How can I get position in the original xml file of an xml tag after deserialization into a .NET object using XmlSerializer ?
Here is an example XML
Another, more simple approach: Let the deserializer do the work.
Add LineInfo and LinePosition properties to all classes for which you would like to have position information:
[XmlRoot("Root")]
public class AddressDetails
{
[XmlAttribute]
public int LineNumber { get; set; }
[XmlAttribute]
public int LinePosition { get; set; }
...
}
This of course can be done by subclassing.
Load an XDocument with LoadOptions.SetLineInfo.
Add LineInfo and LinePosition attributes to all elements:
foreach (var element in xdoc.Descendants())
{
var li = (IXmlLineInfo) element;
element.SetAttributeValue("LineNumber", li.LineNumber);
element.SetAttributeValue("LinePosition", li.LinePosition);
}
Deserializing will populate LineInfo and LinePosition.
Cons:
I tried several approaches. Two of them:
Implement IXmlSerializable. In principle this would work, but then one would have to implement the complete deserialization mechanism.
Deserialize using an XmlReader, save a reference to that XmlReader at some static location, and in the constructor of classes that are deserialized, access that XmlReader and retrieve line and position information. Cons: static; relatively complicated.
I did not find a way to get more information than start line and position, but honestly, that’s enough for my use case.
Currently I think I will use the following approach.
Load the XML file with line information:
XDocument xdoc = XDocument.Parse(xml, LoadOptions.SetLineInfo);
To all elements in xdoc, add a new attribute (e.g. NewId) that allows to identify the elements, e.g. give the first element a 0, the second a 1, etc.
Make sure all classes that are deserialized (in your case AddressDetails) will have such a NewId, e.g.:
[XmlAttribute]
public int NewId { get; set; }
This can be done by deriving from a common base class, and even when xsd.exe is used to create the classes, this is possible because it will create all classes as partial (e.g. a partial class foo : MyBaseClassElement { } needs to be added somewhere for all classes).
After deserializing, for each element that has been deserialized as a class object, the XElement from which it has been deserialized can be looked up using NewId. (To speed up the look up, one can use a List<XElement> that contains all XElement such that NewId is the index in that list.)
Casting the XElement to IXmlLineInfo will give line and position information.
For elements that have not been deserialized as a class object (e.g. Number in your example) and for attributes, first look up the XElement that contains a NewId (e.g. AdressDetails), then query the element’s or attribute’s line information:
XElement element = ...;
XNode descendant = element.Descendants(childElementOrAttributetName).FirstOrDefault();
return descendant as IXmlLineInfo;
Cons: