CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 2 of 2
  1. #1
    Join Date
    Jun 2010
    Posts
    1

    Please help with reading tricky xml

    The Xml:

    Micfosoft(TM)'s modified Books.xml:

    The price attribute of the last book item was modified by me, now it appears two times,
    once as attribute of book and second time as a separate item inside the book item.

    Code:
    <?xml version='1.0'?>
       <!-- This file represents a fragment of a book store inventory database -->
       <bookstore>
         <book genre="autobiography">
           <title>The Autobiography of Benjamin Franklin</title>
           <author>
             <first-name>Benjamin</first-name>
             <last-name>Franklin</last-name>
           </author>
           <price>8.99</price>
         </book>
         <book genre="novel">
           <title>The Confidence Man</title>
           <author>
             <first-name>Herman</first-name>
             <last-name>Melville</last-name>
           </author>
           <price>11.99</price>
         </book>
         <book genre="philosophy" price="best">
           <title>The Gorgias</title>
           <author>
             <name>Plato</name>
           </author>
           <price>9.99</price>
         </book>
       </bookstore>
    My code:

    Code:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using Books.Properties;
    using System.Data.SqlClient;
    using System.Configuration;
    using System.Xml;
    using System.Windows.Forms;
    using System.Xml.Schema;
    using System.IO;
    
    namespace Books
    {
        public partial class Books : Form
        {
            XmlDataDocument myXmlDataDocument = new XmlDataDocument();
            public Books()
            {
                InitializeComponent();
    
                DataSet ds = new DataSet("Books DataSet");
    
                ds.ReadXml(@"../../Books.xml", XmlReadMode.InferSchema);
    
                dataGridView.DataSource = ds;
    
                dataGridView.DataMember = "book";
            }
        }
    }
    The runtime error is of course:

    http://i50.tinypic.com/2hezh8w.png

    Column name 'price' defined for different mapping types.

    Please help me find out how to define/refer in the code to this prices to be possible to read the xml!
    I've been searching thorough google, but the most people were able to come up is "Change the xml", the problem is that the actual xml that I'm working with, has been written in the same unfortunate fashion, and can not be changed.


    _
    Attached Images Attached Images  

  2. #2
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: Please help with reading tricky xml

    This is simple using XmlSerialization.

    For serialization to work, you create a C# class to represent each level of xml nodes that contain children. For your schema, we need three classes: Bookstore, Book, and Author.

    Here's the code that calls the serialization Bookstore class:

    Code:
    static void Main( string [ ] args )
    {
      var bookStore = BookStore.Read( "bookstore.xml" );
    
      foreach( var book in bookStore.Books )
      {
        DisplayBook( book );
      }
    }
    
    static void DisplayBook( Book book )
    {
      Console.WriteLine( String.Format( "Title: {0}", book.Title ) );
      Console.WriteLine( String.Format( "\tGenre: {0}", book.Genre ) );
      Console.WriteLine( String.Format( "\tPrice: {0}", book.PriceType ) );
    
      if ( !String.IsNullOrEmpty( book.Author.Name ) )
        Console.WriteLine( String.Format( "\tName:  {0}", book.Author.Name ) );
      if ( !String.IsNullOrEmpty( book.Author.NameFirst ) )
        Console.WriteLine( String.Format( "\tFirst: {0}", book.Author.NameFirst ) );
      if ( !String.IsNullOrEmpty( book.Author.NameLast ) )
        Console.WriteLine( String.Format( "\tLast:  {0}", book.Author.NameLast ) );
                
      Console.WriteLine( String.Format( "\tPrice: {0}\n", book.Price ) );
    }
    Here's the output:
    Code:
    Title: The Autobiography of Benjamin Franklin
            Genre: autobiography
            Price: great
            First: Benjamin
            Last:  Franklin
            Price: 8.99
    
    Title: The Confidence Man
            Genre: novel
            Price: average
            First: Herman
            Last:  Melville
            Price: 11.99
    
    Title: The Gorgias
            Genre: philosophy
            Price: best
            Name:  Plato
            Price: 9.99
    The Bookstore class contains a static Read method which does all the work of creating the XmlSerializer class, reading the bookstore.xml file and returning an instance of the BookStore class. Notice the Xml attributes at the top of the class declaration. These are what allows use to define the top level serialization class as well as name the class differently for the xml note (i.e. "bookstore" becomes the BookStore class).
    Code:
        /// <summary>
        /// Serialization class used to read the bookstore.xml file
        /// </summary>
        [Serializable]
        [XmlType( AnonymousType = true )]
        [XmlRoot( ElementName = "bookstore", Namespace = "", IsNullable = false )]
        public class BookStore
        {
            /// <summary/>
            public static BookStore Read( string configFile )
            {
                var serializer = new XmlSerializer( typeof( BookStore ) );
    
                using ( var reader = XmlReader.Create( configFile ) )
                {
                    return ( BookStore ) serializer.Deserialize( reader );
                }
            }
    
            /// <remarks/>
            [XmlElement( "book", IsNullable = false )]
            public Book [ ] Books
            {
                get { return _bookList.ToArray( ); }
                set
                {
                    if ( null == _bookList ) { _bookList = new List<Book>( ); }
                    _bookList.AddRange( value );
                }
            }
    
            private List<Book> _bookList = new List<Book>( );
        }
    Notice how the books node has been represented as an Array of Book objects? You might also notice the XmlElement attribute has specified "book" to coincide with the 'book' node. If we didn't specify 'book', the serializer would attempt to look for a 'Book' node and fail to load. Lastly, notice how a generic List<Book> is used rather than a raw array. This allows us to call the Books property and use it without having to check for null first. Using the List<Book> guarantees that the Books property will never be null.

    On to the Book class:
    Code:
        /// <remarks/>
        [Serializable]
        [XmlType( AnonymousType = true )]
        public class Book
        {
            /// <remarks/>
            [XmlAttribute( AttributeName = "genre" )]
            public Genres Genre { get { return _genre; } set { _genre = value; } }
    
            /// <remarks/>
            [XmlAttribute( AttributeName = "price" )]
            public PriceTypes PriceType { get { return _priceType; } set { _priceType = value; } }
    
            [XmlElement( "title", IsNullable = false )]
            public string Title { get { return _title; } set { _title = value; } }
    
            [XmlElement( "author", IsNullable = false )]
            public Author Author { get { return _author; } set { _author = value; } }
    
            [XmlElement( "price", IsNullable = false )]
            public double Price { get { return _price; } set { _price = value; } }
    
            private Genres _genre;
            private PriceTypes _priceType;
            private string _title;
            private Author _author;
            private double _price;
        }
    Notice how the properties are decorated with either XmlAttribute or XmlElement C# attributes? These are what defines whether the xml item is an attribute or an element. This also allows us to solve your particular problem because it's okay to have an attribute with the same name as an element in the same parent node. The XmlSerializer reads it fine. Rather than specifying a string type for Genres and [the attribute] price, I've taken the liberty to define a couple of enums (Genres and PriceTypes). If you have a fixed number of both, I would recommend taking the enum approach (vs. just leaving genre and price as a string type).

    Finally, the Author class. Nothing new here.
    Code:
        /// <remarks/>
        [Serializable]
        [XmlType( AnonymousType = true )]
        public class Author
        {
            [XmlElement( "first-name", IsNullable = false )]
            public string NameFirst { get { return _nameFirst; } set { _nameFirst = value; } }
    
            [XmlElement( "last-name", IsNullable = false )]
            public string NameLast { get { return _nameLast; } set { _nameLast = value; } }
    
            [XmlElement( "name", IsNullable = false )]
            public string Name { get { return _name; } set { _name = value; } }
            
            private string _name;
            private string _nameFirst;
            private string _nameLast;
        }
    See the attachment for the complete source.


    __________________________________________________________
    Arjay

    See my latest series on using WCF to communicate between a Windows Service and WPF task bar application.
    Tray Notify - Part I Tray Notify - Part II

    Need a little help with Win32 thread synchronization? Check out the following CG articles and posts:
    Sharing a thread safe std::queue between threads w/progress bar updating
    Simple Thread: Part I Simple Thread: Part II
    Win32 Thread Synchronization, Part I: Overview Win32 Thread Synchronization, Part 2: Helper Classes

    www.iridyn.com




    ...
    Attached Files Attached Files
    Last edited by Arjay; June 4th, 2010 at 05:02 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured