Click here to Skip to main content
15,886,137 members
Articles / Programming Languages / XML

Towards a Declarative SAX Framework : Part 1 - A Simple SAX-to-C#-Mapping

Rate me:
Please Sign up or sign in to vote.
4.51/5 (15 votes)
20 May 200422 min read 62.8K   893   33  
This article demonstrates a simple but flexible way to read XML content without DOM
#region Copyright
/*********************************************************************************
 * Copyright (c) 2004 by Martin Friedrich										 *
 * Permission to freely distribute, modify and compile these sources and		 *
 * binaries as long as this copyright notice is included.						 *
 * Use of code on your risk!													 *
 * *******************************************************************************/
#endregion

using System;
using System.Collections;
using System.Reflection;

namespace SaxParserSupport
{
	namespace Impl 
	{
		/// <summary>
		/// <p>Implements the <i>XMLSimpleElement</i> interface.</p>
		/// <p>By convention, the alias for the XMLSchema-instance
		/// namespace is assumed to be "xsi".</p>
		/// <p>Attribute names are mapped in the following way:
		/// <list type="bullet">
		/// <item>An attribute is mapped to a C# property with the same name as
		/// the attribute, except for the character ":" being repaced by "_". If
		/// no such property is found, an exception is thrown.</item>
		/// <item>The class of the property must provide a constructor taking a string
		/// as parameter. Each value of this string must be a valid representation of
		/// a property instance. Constructors may throw any exceptions.</item>
		/// <item>Attribute names beginning with "xmlns:" are treated differently in 
		/// that they are not mapped to any property, but instead are internally processed
		/// for namespace specification.</item>
		/// <item>Also, the "xsi:schemaLocation" and "xsi:noNamespaceSchemaLocation"
		/// attributes are processed internally and are not mapped to any property.</item>
		/// </list></p>
		/// </summary>
		public abstract class XMLSimpleElementImpl : XMLSimpleElement
		{

			/// <summary>
			/// Constructs a new instance
			/// </summary>
			protected XMLSimpleElementImpl()
			{
			}

			/// <summary>
			/// <p>Sets a value for a given attribute. The attrbute name is in the
			/// same form as it appears in a XML content.</p>
			/// </summary>
			/// <remarks>
			/// This method may throw exceptions related to the reflective interface of
			/// C#. Additionally, arbitrary exceptions may be thrown by the attribute's
			/// constructor.
			/// </remarks>
			/// <param name="name">The name of the attribute</param>
			/// <param name="value">The value of the attribute in a string form.</param>
			public void setAttribute( string name, string value )
			{
				switch ( name ) 
				{
					case "xsi:schemaLocation":
						string[] tokens = value.Split( null );
						for ( int i = 0; i < tokens.Length; i += 2 ) 
						{
							_namespacelocations.Add( tokens[ i ], tokens[ i + 1 ] );
						}
						break;
					case "xsi:noNamespaceSchemaLocation":
						_namespacelocations.Add( "", value );
						break;
					default:
						if ( name.StartsWith( "xmlns:" ) ) 
						{
							string nsname = name.Substring( 6 );
							_namespaces.Add( nsname, value );
						} 
						else 
						{
							Type thistype = GetType();
							string tgtname = transcodeAttributeName( name, true );
							PropertyInfo pi = thistype.GetProperty( tgtname );
							if ( pi != null ) 
							{
								Type[] argtypes = new Type[ 1 ];
								argtypes[ 0 ] = typeof( string );
								ConstructorInfo ci = pi.PropertyType.GetConstructor( argtypes );
								if ( ci != null ) 
								{
									object[] args = new object[ 1 ];
									args[ 0 ] = value;
									pi.SetValue( this, ci.Invoke( args ), null );
								} else throw new NotImplementedException( "Property \'" + name + "\' ctor is missing" );
							} else throw new NotImplementedException( "Property \'" + name + "\' not found" );
						}
						break;
				}
			}

			/// <summary>
			/// Returns the qualified tag name as it appears in XML content, i.e.
			/// prefixed with namespace alias instead of the namespace itself.
			/// </summary>
			/// <returns>The fully qualified tag name</returns>
			public string getName()
			{
				return _name;
			}
		
			/// <summary>
			/// Sets the fully qualified tag name. If the XML element represented
			/// belongs to the default namespace, the tag name is the local name.
			/// </summary>
			/// <param name="n">The tag name</param>
			public void setName( string n ) 
			{
				_name = n;
			}

			/// <summary>
			/// Returns the owning (parent) <tt>XMLElement</tt> instance. For 
			/// instances of  <tt>XMLDocument</tt> this is <tt>null</tt>.
			/// </summary>
			/// <returns>The owning element</returns>
			public XMLElement getOwner()
			{
				return _owner;
			}
		
			/// <summary>
			/// Sets the owning <tt>XMLElement</tt> instance.
			/// </summary>
			/// <param name="se">The owning <tt>XMLElement</tt> instance. If 
			/// <tt>this</tt>  previous ownership relations are erased.</param>
			public void setOwner( XMLElement se )
			{
				if ( _owner != null ) _owner.removeElement( this );
				_owner = se;
				if ( _owner != null ) _owner.addElement( this );
			}

			/// <summary>
			/// Outputs this instance to a stream. The stream must be opened for
			/// writing. All attributes including namespace declarations etc. are
			/// written out. 
			/// </summary>
			/// <remarks>
			/// This method may throw any exception related to stream output.
			/// </remarks>
			/// <param name="ostr">The stream to be used for output</param>
			public virtual void write( System.IO.StreamWriter ostr )
			{
				ostr.Write( "<{0}", getName() );
				writeAttributes( ostr );
				ostr.WriteLine( " />" );
			}

			/// <summary>
			/// Writes all attributes of this <tt>XMLSimpleAttribute</tt> instance
			/// to a stream
			/// </summary>
			/// <remarks>
			/// This method may throw any exception related to stream output
			/// </remarks>
			/// <param name="ostr">The stream to be used for output</param>
			protected void writeAttributes( System.IO.StreamWriter ostr )
			{
				foreach ( string i in _namespaces.Keys ) 
				{
					ostr.Write( " xmlns:{0}=\"{1}\"", i, _namespaces[ i ] );
				}
				if ( _namespacelocations.Count > 0 ) {
					string ntns = (string ) _namespacelocations[ "" ];
					if ( ntns != null ) ostr.Write( "xsi:noTargetNamespaceLocation=\"{0}\"", ntns );
					System.Text.StringBuilder buff = new System.Text.StringBuilder();
					foreach ( string i in _namespacelocations.Keys ) 
					{
						if ( i != string.Empty ) 
						{
							buff.Append( i ).Append( ' ' ).Append( _namespacelocations[ i ] );
						}
					}
					if ( buff.Length > 0 ) ostr.Write( " xsi:schemaLocation=\"{0}\"", buff );
				}
				Type thistype = GetType();
				foreach( PropertyInfo i in thistype.GetProperties(BindingFlags.Instance|BindingFlags.Public) )
				{
					object val = i.GetValue( this, null );
					if ( val != null ) ostr.Write( " {0}=\"{1}\"", transcodeAttributeName( i.Name, false ), transcode( val.ToString() ) );
				}
			}

			/// <summary>
			/// This method is called when the current elements is closed. Inheriting
			/// classes can override this method to process an element in the context
			/// of it's parent element.
			/// </summary>
			/// <param name="owner">The parent, i.e. owning, instance of <tt>XMLElement</tt></param>
			public virtual void onElementEnd( SaxParserSupport.XMLElement owner ) 
			{
			}

			/// <summary>
			/// Replaces characters &#34;, &#60;, &#62; and &#38; in the given string
			/// </summary>
			/// <param name="s">The string to be transcoded</param>
			/// <returns></returns>
			protected virtual string transcode( string s )
			{
				return s.Replace( "\"", "&#34;" ).Replace( "<", "&#60;" ).Replace( ">", "&#62;" )
					.Replace( "&", "&#38;" );
			}

			/// <summary>
			/// Converts a XML attribute name into a valid C# member name or vice versa
			/// </summary>
			/// <param name="n">The attribute name</param>
			/// <param name="fromXML">The direction ov conversion</param>
			/// <returns></returns>
			protected string transcodeAttributeName( string n, bool fromXML )
			{
				if ( fromXML ) 
				{
					return n.Replace( ":", "_" );
				}
				else 
				{
					return n.Replace( "_", ":" );
				}
			}

			/// <summary>
			/// ***PRIVATE***
			/// </summary>
			private XMLElement _owner = null;

			/// <summary>
			/// ***PRIVATE***
			/// </summary>
			private string _name;

			/// <summary>
			/// ***PRIVATE***
			/// </summary>
			private Hashtable _namespacelocations = new Hashtable();

			/// <summary>
			/// ***PRIVATE***
			/// </summary>
			private Hashtable _namespaces = new Hashtable();
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Germany Germany

Still lacking an university degree in computer science, I have 20 years of experience in software development and implementation. Having expert knowledge in object-oriented programming languages like C++, Java and C# on Windows, LINUX and UNIX platforms, I participated in multiple research projects at the University of Oldenburg. During these assignments, I was trusted with implementation of a graphical editor for specification languages like CSP or Z and a prototypical tool for workflow data distribution and analysis. I gained experiences in a widespread spectrum of CS and software development topics, ranging from compiler construction across data base programming to MDA. My research interests include questions of graphical user interface design and component-based systems.


I consider myself an old-school CS geek. While I admit that simple tasks do not pose much of a problem and can be done in quick and efficient manner, it's the challenging ones that appeal to me. If you are looking for a skillful employee - be it on a permanent, contract or freelance basis - and if you can live with a lacking university degree, why not contacting me?


Comments and Discussions