Search code examples
c#xmlxml-declaration

Get xml node by attribute value and de-serialize that node


I have two XML file as shown below

Format 1:

<Template Type="Print">
  <PrintWidth>7</PrintWidth>
  <PrintHeight>5</PrintHeight>
</Template>

Format 2:

<Templates>
  <Template Type="Print">
    <PrintWidth>7</PrintWidth>
    <PrintHeight>5</PrintHeight>
  </Template>
  <Template Type="Print">
    <PrintWidth>7</PrintWidth>
    <PrintHeight>5</PrintHeight>
  </Template>
</Templates>

I have created Mapping Class for the Format 1 as below:

public class Template 
 {             
        private double _printWidth;        
        private double _printHeight;         

        /// <summary>
        /// Print width in inches
        /// </summary>
        [System.Xml.Serialization.XmlAttributeAttribute()]
        public double PrintWidth {
            get {
                return this._printWidth;
            }
            set {
                this._printWidth = value;
                this.RaisePropertyChanged("PrintWidth");
            }
        }                

        [System.Xml.Serialization.XmlAttributeAttribute()]
        public double PrintHeight {
            get {
                return this._printHeight;
            }
            set {
                this._printHeight = value;
                this.RaisePropertyChanged("PrintHeight");
            }
        }        
}

I wanted to desirealize only single node of XML in Format 2 that is having Type="Print" into Template class. Is there any generic way with which I can deserialize both XML Files (Foarmat 1 and a single node of Format 2) to Template class?


Solution

  • Yes, that can be done by combining Linq to XML with XmlSerializer:

    1. Load the XML into an XDocument

    2. Use linq to find an appropriate element with the appropriate attribute in the XML element hierarchy.

    3. Deserialize the selected element, using XElement.CreateReader() to pass an XmlReader that reads the element and its descendants to the serializer.

    Thus, for instance:

        public static void Test()
        {
            string xml1 = @"<Template Type=""Print"">
              <PrintWidth>7</PrintWidth>
              <PrintHeight>5</PrintHeight>
            </Template>";
    
            string xml2 = @"<Templates>
              <Template Type=""Print"">
                <PrintWidth>7</PrintWidth>
                <PrintHeight>5</PrintHeight>
              </Template>
              <Template Type=""Print"">
                <PrintWidth>7</PrintWidth>
                <PrintHeight>5</PrintHeight>
              </Template>
            </Templates>";
    
            var template1 = ExtractTemplate(xml1);
    
            var template2 = ExtractTemplate(xml2);
    
            Debug.Assert(template1 != null && template2 != null
                && template1.PrintWidth == template2.PrintWidth
                && template1.PrintWidth == 7
                && template1.PrintHeight == template2.PrintHeight
                && template1.PrintHeight == 5); // No assert
        }
    
        public static Template ExtractTemplate(string xml)
        {
            // Load the XML into an XDocument
            var doc = XDocument.Parse(xml);
    
            // Find the first element named "Template" with attribute "Type" that has value "Print".
            var element = doc.Descendants("Template").Where(e => e.Attributes("Type").Any(a => a.Value == "Print")).FirstOrDefault();
    
            // Deserialize it to the Template class
            var template = (element == null ? null : element.Deserialize<Template>());
    
            return template;
        }
    

    Using the extension method:

    public static class XObjectExtensions
    {
        public static T Deserialize<T>(this XContainer element)
        {
            return element.Deserialize<T>(new XmlSerializer(typeof(T)));
        }
    
        public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
        {
            using (var reader = element.CreateReader())
            {
                object result = serializer.Deserialize(reader);
                if (result is T)
                    return (T)result;
            }
            return default(T);
        }
    }
    

    Incidentally, your Template class has a bug: you need to mark PrintWidth and PrintHeight with [XmlElement] rather than [XmlAttribute] to deserialize that XML properly:

    public class Template
    {
        private double _printWidth;
        private double _printHeight;
    
        /// <summary>
        /// Print width in inches
        /// </summary>
        [System.Xml.Serialization.XmlElementAttribute()]
        public double PrintWidth
        {
            get
            {
                return this._printWidth;
            }
            set
            {
                this._printWidth = value;
                this.RaisePropertyChanged("PrintWidth");
            }
        }
    
        [System.Xml.Serialization.XmlElementAttribute()]
        public double PrintHeight
        {
            get
            {
                return this._printHeight;
            }
            set
            {
                this._printHeight = value;
                this.RaisePropertyChanged("PrintHeight");
            }
        }
    }