// <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
}
}