Click here to Skip to main content
15,891,204 members
Articles / Programming Languages / XML

TreeConfiguration - configuration made as simple as it gets (or sets)

Rate me:
Please Sign up or sign in to vote.
4.66/5 (44 votes)
2 Nov 200514 min read 83.6K   724   67  
Manage configuration data with a few lines of code. Very few.
// <copyright file="XmlConfiguration.cs" company="MetaPropeller" author="Vladimir Klisic">
//
// Copyright 2005 Vladimir Klisic
// mailto:vladimir.klisic@free.fr
//
// This software is provided 'as-is', without any express or implied
// warranty.  In no event will the authors be held liable for any 
// damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose, 
// including commercial applications, and to alter it and redistribute 
// it freely, subject to the following restrictions: 
//
//  1. The origin of this software must not be misrepresented; you must 
//     not claim that you wrote the original software. If you use this 
//     software in a product, an acknowledgment in the product documentation 
//     would be appreciated but is not required. 
//  2. Altered source versions must be plainly marked as such, and must 
//     not be misrepresented as being the original software. 
//  3. This notice may not be removed or altered from any source distribution. 
//
// </copyright>
//
// <history>
//  <change who=�Vladimir Klisic� date=�2005.10.23�>Skipping XML comments when reading data</change>
//  <change who=�Vladimir Klisic� date=�2005.02.16�>Initial version</change>
// <history>

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Schema;
using System.ComponentModel;
using System.Globalization;
using System.Diagnostics;

namespace MetaPropeller.Configuration
{
  /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="T:XmlConfiguration"]/*'/>
  public class XmlConfiguration : TreeConfiguration
  {
    #region XML elements definitions
    private const string METAPROPELLER_SCHEMA_NAMESPACE = "urn:xmlns:metapropeller-com:configuration";
    private const string ROOT_ELEMENT_NAME = "Configuration";
    private const string NODE_ELEMENT_NAME = "Node";
    private const string KEY_ELEMENT_NAME = "Key";
    private const string TYPE_ATTRIBUTE = "type";
    private const string NAME_ATTRIBUTE = "name";
    private const string CULTURE_ATTRIBUTE = "culture";
    private const string VALUE_ATTRIBUTE = "value";
    #endregion

    #region Members
    private string _filename;
    #endregion

    #region Constructors
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:XmlConfiguration"]/*'/>
    public XmlConfiguration(string filename, string configurationName)
      : base(configurationName)
    {
      _filename = filename;
      this.Culture = CultureInfo.CurrentCulture.Name;
    }
    #endregion

    #region Properties
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="P:Filename"]/*'/>
  	public string Filename
  	{
  		get { return _filename; }
			set { _filename = value; }
  	}
		#endregion

    #region Methods
    #region Overrides
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:Load"]/*'/>
    public override bool Load()
    {
      XmlTextReader tr = null;
      XmlValidatingReader vr = null;
      XmlReader schemaReader = null;
      try
      {
        if (!File.Exists(_filename))
          return false;
        tr = new XmlTextReader(_filename);
        vr = new XmlValidatingReader(tr); 
        schemaReader = new XmlTextReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("TreeConfiguration.TreeConfiguration.xsd"));
        vr.Schemas.Add(METAPROPELLER_SCHEMA_NAMESPACE, schemaReader); 
        vr.ValidationType = ValidationType.Schema; 
        vr.ValidationEventHandler += new ValidationEventHandler(ValidationCallback); 

        // if we get past this, we know for sure configuration file is valid 
        XPathDocument doc = new XPathDocument(vr); 
        XPathNavigator nav = doc.CreateNavigator(); 
        bool isLoaded = false;
        nav.MoveToRoot();
        if (nav.MoveToFirstChild())
        {
          while (nav.NodeType != XPathNodeType.Element || nav.Name != ROOT_ELEMENT_NAME)
            nav.MoveToNext();
          if (nav.MoveToFirstChild())
          {
            while (nav.NodeType != XPathNodeType.Element || nav.Name != NODE_ELEMENT_NAME)
              nav.MoveToNext();
            // Assembly resolver helps TypeConverter construct types that otherwise must be qualified using a 
            // strong assembly name (such as types from System.Drawing namespace, for example).
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(this.OnTypeConverterResolveAssembly);
            string cultureName = nav.GetAttribute(CULTURE_ATTRIBUTE, String.Empty);
						if (cultureName.Length > 0) // Configuration has one by default; overwrite it only if there was one explicitly stored
							_rootNode.Culture = cultureName;
            CultureInfo cultureInfo = _rootNode.Culture.Length > 0? new CultureInfo(_rootNode.Culture) : CultureInfo.CurrentCulture;
            isLoaded = LoadNode(_rootNode, nav, cultureInfo);
            AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(this.OnTypeConverterResolveAssembly);
          }
        } 
        return isLoaded;
      }
      catch (XmlException e)
      {
        Debug.WriteLine("Exception: {0}", e.ToString());
        return false;
      }
      catch (XPathException xe)
      {
        Debug.WriteLine("Exception: {0}", xe.ToString());
        return false;
      }
      finally
      {
        if (tr != null) tr.Close();
        if (vr != null) vr.Close();
        if (schemaReader != null) schemaReader.Close();
      }
    }

    private Assembly OnTypeConverterResolveAssembly(object sender, ResolveEventArgs args)
    {
      AppDomain domain = (AppDomain) sender;

      foreach (Assembly asm in domain.GetAssemblies())
      {
        if (asm.GetName().Name == args.Name)
          return asm;
      }
      return null;
    }

    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:Save"]/*'/>
    public override bool Save()
    {
      FileStream fs = null;
      StreamWriter sw = null;
      XmlTextWriter wr = null;
      try
      {
        FileInfo fi = new FileInfo(_filename);
        FileMode fileMode = FileMode.CreateNew;
        if (fi.Exists)
          fileMode |= FileMode.Truncate;
        else
          Directory.CreateDirectory(Path.GetDirectoryName(fi.ToString()));
          
        fs = fi.Open(fileMode, FileAccess.ReadWrite, FileShare.Read);
        sw = new StreamWriter(fs, Encoding.UTF8);
        wr = new XmlTextWriter(sw);
        wr.Formatting = Formatting.Indented;
        wr.Namespaces = true;

        wr.WriteStartDocument(); 

        wr.WriteStartElement(ROOT_ELEMENT_NAME);
        wr.WriteAttributeString(NAME_ATTRIBUTE, this.Name);
        wr.WriteAttributeString("version", "1.0");
        wr.WriteAttributeString("xmlns", null, null, METAPROPELLER_SCHEMA_NAMESPACE);
        wr.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");

        CultureInfo cultureInfo = _rootNode.Culture.Length > 0? new CultureInfo(_rootNode.Culture) : CultureInfo.CurrentCulture;
        SaveNode(_rootNode, wr, cultureInfo);

        wr.WriteEndElement(); // Configuration
        wr.WriteEndDocument();
      }
      catch (XmlException e)
      {
        Debug.WriteLine("Exception: {0}", e.ToString());
        return false;
      }
      catch (XPathException xe)
      {
        Debug.WriteLine("Exception: {0}", xe.ToString());
        return false;
      }
      finally
      {
        if (sw != null) sw.Close();
        if (wr != null) wr.Close();
        if (fs != null) fs.Close();
      }
      return true;
    }
    #endregion
    #region Regular methods
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:Load1"]/*'/>
    public bool Load(string configurationFile) 
    {
      _filename = configurationFile; 
      return Load(); 
    }
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:LoadNode"]/*'/>
    protected bool LoadNode(ConfigurationNode node, XPathNavigator nav, CultureInfo cultureInfo)
    {
      for (bool moved = nav.MoveToFirstChild(); moved; moved = nav.MoveToNext())
      {
        if (nav.NodeType != XPathNodeType.Element)
          continue;
        if (nav.Name == NODE_ELEMENT_NAME)
        {
          string nodeName = nav.GetAttribute(NAME_ATTRIBUTE, String.Empty);
          ConfigurationPath path = new ConfigurationPath(nodeName);
          ConfigurationNode n = node.CreateSubNode(path);
          string cultureName = nav.GetAttribute(CULTURE_ATTRIBUTE, String.Empty);
          n.Culture = cultureName;
          if (n.Culture.Length > 0 && String.Compare(n.Culture, cultureInfo.Name, true) != 0)
            LoadNode(n, nav, new CultureInfo(n.Culture));
          else
            LoadNode(n, nav, cultureInfo);
          nav.MoveToParent();
        }
        else if (nav.Name == KEY_ELEMENT_NAME)
        {
          string key = nav.GetAttribute(NAME_ATTRIBUTE, String.Empty);
          string typeName = nav.GetAttribute(TYPE_ATTRIBUTE, String.Empty);
          string value = nav.GetAttribute(VALUE_ATTRIBUTE, String.Empty);
          object obj = null;
          Type t = Type.GetType(typeName, false);   
          if (t != null)
          {
            TypeConverter conv = TypeDescriptor.GetConverter(t);
            Debug.Assert(conv != null && conv.CanConvertFrom(typeof(string)));
            obj = conv.ConvertFromString(null, cultureInfo, value);
          }
          if (obj != null)
            node[key] = obj;
          else
            Debug.Assert(false, "Couldn't construct object from type", typeName);
        }
        else
          Debug.Assert(false, "Unknown element", nav.Name);
      }
      return true;
    }
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:Save1"]/*'/>
    public bool Save(string configurationFile) 
    {
      _filename = configurationFile; 
      return Save(); 
    }    
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:SaveNode"]/*'/>
    protected bool SaveNode(ConfigurationNode node, XmlTextWriter wr, CultureInfo cultureInfo)
    {
      if (node.Empty)
        return true; // done here

      wr.WriteStartElement(NODE_ELEMENT_NAME);
      wr.WriteAttributeString(NAME_ATTRIBUTE, node.Name);
      if (node.Culture.Length > 0)
        wr.WriteAttributeString(CULTURE_ATTRIBUTE, node.Culture);

      CultureInfo nodeCulture;
      if (node.Culture.Length > 0 && String.Compare(node.Culture, cultureInfo.Name, true) != 0)
        nodeCulture = new CultureInfo(node.Culture);
      else
        nodeCulture = cultureInfo;

      foreach (ConfigurationNode n in node.Nodes)
      {
        SaveNode(n, wr, nodeCulture);
      }

      foreach (string key in node.Keys)
      {
        object obj = node[key];
        string typeName = obj.GetType().FullName;
        TypeConverter conv = TypeDescriptor.GetConverter(obj.GetType());
        if (conv == null || !conv.CanConvertTo(typeof(string)))
        {
          Debug.Assert(false, "Failed to convert type: " + typeName);
          continue;
        }
        string value = conv.ConvertToString(null, nodeCulture, obj);
        wr.WriteStartElement(KEY_ELEMENT_NAME);
        wr.WriteAttributeString(NAME_ATTRIBUTE, key);
        // Only partial assembly names get stored. Assembly resolver will help finding the right
        // assembly if TypeConverter can't do it for some reason. This works for all tested types,
        // but you can change this behavior by always storing the full name.
        wr.WriteAttributeString(TYPE_ATTRIBUTE, obj.GetType().FullName + ", " + obj.GetType().Assembly.GetName().Name);
        wr.WriteAttributeString(VALUE_ATTRIBUTE, value != null? value : string.Empty);
        wr.WriteEndElement(); // Key
      }
      wr.WriteEndElement(); // Node
      return true;
    }
    /// <include file='docs/XmlConfigurationDoc.xml' path='doc/members/member[@name="M:ValidationCallback"]/*'/>    
    protected void ValidationCallback(object sender, ValidationEventArgs args)
    {
      if (args.Severity == XmlSeverityType.Warning)
      {
        Debug.WriteLine(args.Message);

      }
      else if (args.Severity == XmlSeverityType.Error)
      {
        Debug.WriteLine(args.Message);
      }
    }
    #endregion
    #endregion
  }
}

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
France France
Vladimir Klisic is a half-human, half-software development engineer, currently working for Trema Laboratories in southeastern France (Sophia-Antipolis).

Comments and Discussions