locked
LINQ to XML question: how to convert non-logical XML to Object? RRS feed

  • Question

  • Have a XML:

    <chapter>
      <header name="1" />
      <param value="1-123">
      <param value="1-456">
      <param value="1-789">
      <header name="2" />
      <param value="2-123">
      <param value="2-456">
      <param value="2-789">
    </chapter>
    
    

    But not with the correct structure, as this:

    <chapter>
      <header name="1">
       <param value="1-123">
       <param value="1-456">
       <param value="1-789">
      </header>
      <header name="2">
       <param value="2-123">
       <param value="2-456">
       <param value="2-789">
      </header>
    </chapter>
    
    

    And Chapter object is:

    class Chapter
      {
        public List<Header> Headers;
      }
    
    class Header
      {
        public string Name { get; set; }
        public List<Param> Params;
      }
    
    class Param
      {
        public string Value { get; set; }
      }
    
    

    How to correctly transform first variant of XML to object "Chapter", using LINQ ? In DOM i usу foreach:

    foreach (XmlNode node in chapter) {<br/>
      if (node.Name == "header")  // current Header is...<br/>
      else if(node.Name == "param") // add object Param to current Header<br/>
    }<br/>
    
    
    p.S. Sorry for my english.

    Thursday, November 11, 2010 12:27 PM

Answers

  • Here is an alternative grouping approach in case the value matching I did in the other sample (i.e. param.Attribute("value").Value.StartsWith(header.Attribute("name").Value)) is not possible:

          Chapter chap = new Chapter()
          {
            Headers =
             (from param in doc.Element("chapter").Elements("param")
              group param by param.ElementsBeforeSelf("header").Last() into g
              select new Header()
              {
                Name = (string)g.Key.Attribute("name"),
                Params =
                 (from p in g
                 select new Param()
                 {
                   Value = (string)p.Attribute("value")
                 }).ToList()
              }).ToList()
          };
    

    See also http://msmvps.com/blogs/martin_honnen/archive/2009/11/27/grouping-with-linq-to-xml.aspx

     


    MVP Data Platform Development My blog
    • Proposed as answer by Leonid Ganeline Friday, November 12, 2010 3:40 AM
    • Marked as answer by Auximen Friday, November 12, 2010 8:16 AM
    Thursday, November 11, 2010 5:28 PM

All replies

  • Sorry, but what you have posted as the input is not XML as all those param elements are not closed. So please show us a well-formed XML input sample.

    And as for the grouping, is it ensured that all "param" elements that belong to a "header" element have a "value" attribute that starts with the digit or number the "header" has as its "name" attribute value? In your sample it seems that the number is present and could be used as a grouping criteria but I am not sure that is the case in your real data.

     

    With that assumption one could try

          XDocument doc = XDocument.Load(@"chapter.xml");
          Chapter chap = new Chapter() {
            Headers = 
             (from header in doc.Element("chapter").Elements("header")
             select new Header() {
               Name = (string)header.Attribute("name"),
               Params = 
                (from param in header.ElementsAfterSelf("param")
                 where param.Attribute("value").Value.StartsWith(header.Attribute("name").Value)
                 select new Param() {
                   Value = (string)param.Attribute("value")
                 }).ToList()
             }).ToList()
          };
    
          foreach (Header header in chap.Headers)
          {
            Console.WriteLine(header.Name);
            foreach (Param p in header.Params)
            {
              Console.WriteLine("\t{0}", p.Value);
            }
            Console.WriteLine();
          }

    where chapter.xml is

    <chapter>
     <header name="1" />
     <param value="1-123"/>
     <param value="1-456"/>
     <param value="1-789"/>
     <header name="2" />
     <param value="2-123"/>
     <param value="2-456"/>
     <param value="2-789"/>
    </chapter>

     

     


    MVP Data Platform Development My blog
    • Edited by Martin Honnen Thursday, November 11, 2010 1:28 PM adding sample code
    • Proposed as answer by Leonid Ganeline Friday, November 12, 2010 3:39 AM
    Thursday, November 11, 2010 1:21 PM
  • >> Sorry, but what you have posted as the input is not XML as all those param elements are not closed.

    >> So please show us a well-formed XML input sample.

    Yes, of course, it is a typo, I apologize for the incorrect syntax for the example file provided in the message.

    <param value="ABCDEF"/>

    I've been using as an example of a simplified XML-structure, in reality, as the tag "header" can be a single tag <header /> or can be a tag with parameters <header name="" caption="" id=""/>, as well, and the tag "param" <param value="ABCDEF" /> . The fact that a software designer improperly packed object in XML, and instead invest <param /> tags in <header />, he made them equal and consistent as a paper book.

    In the DOM, I solved the problem by trying all the elements foreach tag <chapter />, hoping that LINQ is a better way.

    Thursday, November 11, 2010 2:11 PM
  • Here is an alternative grouping approach in case the value matching I did in the other sample (i.e. param.Attribute("value").Value.StartsWith(header.Attribute("name").Value)) is not possible:

          Chapter chap = new Chapter()
          {
            Headers =
             (from param in doc.Element("chapter").Elements("param")
              group param by param.ElementsBeforeSelf("header").Last() into g
              select new Header()
              {
                Name = (string)g.Key.Attribute("name"),
                Params =
                 (from p in g
                 select new Param()
                 {
                   Value = (string)p.Attribute("value")
                 }).ToList()
              }).ToList()
          };
    

    See also http://msmvps.com/blogs/martin_honnen/archive/2009/11/27/grouping-with-linq-to-xml.aspx

     


    MVP Data Platform Development My blog
    • Proposed as answer by Leonid Ganeline Friday, November 12, 2010 3:40 AM
    • Marked as answer by Auximen Friday, November 12, 2010 8:16 AM
    Thursday, November 11, 2010 5:28 PM
  • Here is an alternative grouping approach in case the value matching I did in the other sample (i.e. param.Attribute("value").Value.StartsWith(header.Attribute("name").Value)) is not possible:

     

       ...     group param by param.ElementsBeforeSelf("header").Last() into g
    
        ... 

    See also http://msmvps.com/blogs/martin_honnen/archive/2009/11/27/grouping-with-linq-to-xml.aspx

     


    MVP Data Platform Development My blog
    Cool! I like it! So many ways are in Linq

    Leonid Ganeline [BizTalk MVP] Biztalkien blog
    Friday, November 12, 2010 3:47 AM