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: