1 Attachment(s)
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.
_
1 Attachment(s)
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
...