This problem recently came up while discussing a project that will use FpML documents. FpML is an evolving standard that aims to be the industry standard protocol for complex financial products. Its an extensive project with a good deal of support from major players and a sharp talent pool behind it.

I’ll try to explain how code generation facilities built into the .Net framework can provide us with a basic usable class library that encapsulates the FpML protocol.

We begin by downloading the relevant schemas from the FpML website (requires free registration) and extracting them to a local folder (C:\FpML).

image.gif

Then we fire up Visual Studio and create a new Console Application. At this point you maybe wondering if it wouldn’t be simpler to just use XSD.EXE for the code generation. Unfortunately XSD.EXE has difficulty resolving external schemas.

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Microsoft.CSharp;
 
namespace ConsoleApplication1
{
    internal class Program
    {
        public const string rootFolder = @"C:\FpML\xml\";
 
        private static void Main(string[] args)
        {
            XmlSchema rootSchema = GetSchemaFromFile("fpml-main-4-2.xsd");
 
            var schemaSet = new List<XmlSchemaExternal>();
 
            ExtractIncludes(rootSchema, ref schemaSet);
 
            var schemas = new XmlSchemas { rootSchema };
 
            schemaSet.ForEach(schemaExternal => schemas.Add(GetSchemaFromFile(schemaExternal.SchemaLocation)));
 
            schemas.Compile(null, true);
 
            var xmlSchemaImporter = new XmlSchemaImporter(schemas);
 
            var codeNamespace = new CodeNamespace("Hosca.FpML4_2");
            var xmlCodeExporter = new XmlCodeExporter(codeNamespace);
 
            var xmlTypeMappings = new List<XmlTypeMapping>();
 
            foreach (XmlSchemaType schemaType in rootSchema.SchemaTypes.Values)
                xmlTypeMappings.Add(xmlSchemaImporter.ImportSchemaType(schemaType.QualifiedName));
            foreach (XmlSchemaElement schemaElement in rootSchema.Elements.Values)
                xmlTypeMappings.Add(xmlSchemaImporter.ImportTypeMapping(schemaElement.QualifiedName));
 
            xmlTypeMappings.ForEach(xmlCodeExporter.ExportTypeMapping);
 
            CodeGenerator.ValidateIdentifiers(codeNamespace);
 
            foreach (CodeTypeDeclaration codeTypeDeclaration in codeNamespace.Types)
            {
                for (int i = codeTypeDeclaration.CustomAttributes.Count - 1; i >= 0; i--)
                {
                    CodeAttributeDeclaration cad = codeTypeDeclaration.CustomAttributes[i];
                    if (cad.Name == "System.CodeDom.Compiler.GeneratedCodeAttribute")
                        codeTypeDeclaration.CustomAttributes.RemoveAt(i);
                }
            }
 
            using (var writer = new StringWriter())
            {
                new CSharpCodeProvider().GenerateCodeFromNamespace(codeNamespace, writer, new CodeGeneratorOptions());
 
                //Console.WriteLine(writer.GetStringBuilder().ToString());
 
                File.WriteAllText(Path.Combine(rootFolder, "FpML4_2.Generated.cs"), writer.GetStringBuilder().ToString());
            }
 
            Console.ReadLine();
        }
 
        private static XmlSchema GetSchemaFromFile(string fileName)
        {
            using (var fs = new FileStream(Path.Combine(rootFolder, fileName), FileMode.Open))
                return XmlSchema.Read(fs, null);
        }
 
        private static void ExtractIncludes(XmlSchema xmlSchema, ref List<XmlSchemaExternal> schemaList)
        {
            foreach (XmlSchemaExternal include in xmlSchema.Includes)
            {
                if (!schemaList.Select(s => s.SchemaLocation).Contains(include.SchemaLocation))
                    schemaList.Add(include);
 
                if (include.Schema == null)
                {
                    XmlSchema schema = GetSchemaFromFile(include.SchemaLocation);
 
                    ExtractIncludes(schema, ref schemaList);
                }
                else
                    ExtractIncludes(include.Schema, ref schemaList);
            }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework.Config;
using NHibernate.Expression;

namespace SettingsManager
{
    public partial class SettingsManagerException : Exception
    {
        public SettingsManagerException(string message) : base(message)
        {
        }
    }

    public partial class SettingsManager
    {
        private const char BACKSLASH = '\x005c';

        public SettingsManager(string ConnectionString)
        {
            Hashtable properties = new Hashtable();
            properties.Add("hibernate.connection.driver_class", "NHibernate.Driver.SqlClientDriver");
            properties.Add("hibernate.dialect", "NHibernate.Dialect.MsSql2000Dialect");
            properties.Add("hibernate.connection.provider", "NHibernate.Connection.DriverConnectionProvider");
            properties.Add("hibernate.connection.connection_string", ConnectionString);
            InPlaceConfigurationSource source = new InPlaceConfigurationSource();
            source.Add(typeof (ActiveRecordBase), properties);
            ActiveRecordStarter.Initialize(source, typeof (Setting), typeof (Section));
            //ActiveRecordStarter.CreateSchema();
        }

        private static List<T> MakeList<T>(IList<T> sourceList)
        {
            List<T> resultList = sourceList as List<T>;
            if (resultList == null)
                resultList = new List<T>(sourceList);
            return resultList;
        }

        public void SetSetting<T>(string ApplicationName, string SectionPath, string SettingName, T SettingValue)
        {
            SetSetting<T>(ApplicationName, SectionPath, SettingName, SettingValue, DateTime.UtcNow.AddYears(1));
        }

        public void SetSetting<T>(string ApplicationName, string SectionPath, string SettingName, T SettingValue, DateTime ExpiresOnUTC)
        {
            SetSetting(ApplicationName,
                       SectionPath.Split(new char[] {BACKSLASH}, StringSplitOptions.RemoveEmptyEntries),
                       SettingName,
                       SettingValue,
                       ExpiresOnUTC);
        }

        private static Section GetSectionForApplication(string ApplicationName)
        {
            ICriterion[] criteria =
                new ICriterion[] {Expression.IsNull("ParentSection"), Expression.Eq("SectionName", ApplicationName)};
            return Section.FindOne(criteria);
        }

        public void SetSetting<T>(string ApplicationName,
                                  string[] SectionPath,
                                  string SettingName,
                                  T SettingValue,
                                  DateTime ExpiresOnUTC)
        {
            Section foundSection = GetSectionForApplication(ApplicationName);
            if (foundSection == null)
            {
                foundSection = new Section(ApplicationName, null);
                foundSection.Save();
                foundSection = GetSectionForApplication(ApplicationName);
            }
            Section prevSection = foundSection;
            foreach (string sectionPath in SectionPath)
            {
                foundSection =
                    MakeList(foundSection.Sections).Find(
                        delegate(Section sec) { return sec.SectionName.Equals(sectionPath); });
                if (foundSection == null)
                {
                    foundSection = new Section(sectionPath, prevSection);
                    foundSection.Save();
                }
                prevSection = foundSection;
            }
            Setting setting =
                MakeList(foundSection.Settings).Find(
                    delegate(Setting set) { return set.SettingName.Equals(SettingName); });
            if (setting == null)
            {
                setting = new Setting(SettingName, SettingValue.ToString(), ExpiresOnUTC);
                setting.Save();
                foundSection.Settings.Add(setting);
                foundSection.Save();
            }
            else
            {
                setting.SettingValue = SettingValue.ToString();
                setting.ExpiresOnUTC = ExpiresOnUTC;
                setting.Save();
            }
        }

        public T GetSetting<T>(string ApplicationName, string SectionPath, string SettingName)
        {
            return
                GetSetting<T>(ApplicationName,
                              SectionPath.Split(new char[] {BACKSLASH}, StringSplitOptions.RemoveEmptyEntries),
                              SettingName);
        }

        public T GetSetting<T>(string ApplicationName, string[] SectionPath, string SettingName)
        {
            Section foundSection = GetSectionForApplication(ApplicationName);
            foreach (string sectionPath in SectionPath)
            {
                foundSection =
                    MakeList(foundSection.Sections).Find(
                        delegate(Section sec) { return sec.SectionName.Equals(sectionPath); });
                if (foundSection == null)
                    throw new SettingsManagerException("Section not found.");
            }
            Setting setting =
                MakeList(foundSection.Settings).Find(
                    delegate(Setting set) { return set.SettingName.Equals(SettingName); });
            if (setting == null)
                throw new SettingsManagerException("Setting not found.");
            if (setting.ExpiresOnUTC < DateTime.UtcNow)
                throw new SettingsManagerException("Setting has expired.");
            return (T) (object) setting.SettingValue;
        }

        public void DeleteSetting(string ApplicationName, string SectionPath, string SettingName)
        {
            DeleteSetting(ApplicationName,
                          SectionPath.Split(new char[] {BACKSLASH}, StringSplitOptions.RemoveEmptyEntries),
                          SettingName);
        }

        public void DeleteSetting(string ApplicationName, string[] SectionPath, string SettingName)
        {
            Section foundSection = GetSectionForApplication(ApplicationName);
            foreach (string sectionPath in SectionPath)
            {
                foundSection =
                    MakeList(foundSection.Sections).Find(
                        delegate(Section sec) { return sec.SectionName.Equals(sectionPath); });
                if (foundSection == null)
                    throw new SettingsManagerException("Section not found.");
            }
            Setting setting =
                MakeList(foundSection.Settings).Find(
                    delegate(Setting set) { return set.SettingName.Equals(SettingName); });
            if (setting != null)
                setting.Delete();
        }

        public Dictionary<string, string> GetAllSettings(string ApplicationName, string SectionPath)
        {
            return
                GetAllSettings(ApplicationName,
                               SectionPath.Split(new char[] {BACKSLASH}, StringSplitOptions.RemoveEmptyEntries));
        }

        public Dictionary<string, string> GetAllSettings(string ApplicationName, string[] SectionPath)
        {
            Dictionary<string, string> returnDic = new Dictionary<string, string>();
            Section foundSection = GetSectionForApplication(ApplicationName);
            foreach (string sectionPath in SectionPath)
            {
                foundSection =
                    MakeList(foundSection.Sections).Find(
                        delegate(Section sec) { return sec.SectionName.Equals(sectionPath); });
                if (foundSection == null)
                    throw new SettingsManagerException("Section not found.");
            }

            foreach (Setting setting in foundSection.Settings)
                returnDic.Add(string.Concat(foundSection.SectionName, BACKSLASH, setting.SettingName), setting.SettingValue);
            foreach (Section section in foundSection.Sections)
                TraverseSection(section, returnDic);

            return returnDic;
        }

        private static void TraverseSection(Section section, Dictionary<string, string> dic)
        {
            if (section.Sections.Count >= 1)
                foreach (Section sec in section.Sections)
                    TraverseSection(sec, dic);
            else
                foreach (Setting setting in section.Settings)
                    dic.Add(string.Concat(section.SectionName, BACKSLASH, setting.SettingName), setting.SettingValue);
        }
    }

    [ActiveRecord("Section", Schema = "dbo")]
    internal partial class Section : ActiveRecordBase<Section>
    {
        private Section _parentsection;
        private int _sectionId;
        private string _sectionName;
        private IList<Section> _sections = new List<Section>();
        private IList<Setting> _settings = new List<Setting>();

        public Section()
        {
        }

        public Section(string SectionName, Section ParentSection)
        {
            _sectionName = SectionName;
            _parentsection = ParentSection;
        }

        [Property("SectionName", ColumnType = "String", NotNull = true)]
        public virtual string SectionName { get { return _sectionName; } set { _sectionName = value; } }

        [PrimaryKey(PrimaryKeyType.Identity, "SectionId", ColumnType = "Int32")]
        public virtual int SectionId { get { return _sectionId; } set { _sectionId = value; } }

        [HasMany(typeof (Section), ColumnKey = "ParentSectionId", Table = "Section")]
        public virtual IList<Section> Sections { get { return _sections; } set { _sections = value; } }

        [BelongsTo("ParentSectionId", NotNull = false)]
        public virtual Section ParentSection { get { return _parentsection; } set { _parentsection = value; } }

        [HasAndBelongsToMany(typeof (Setting), ColumnRef = "SettingId", ColumnKey = "SectionId", Schema = "dbo",
            Table = "SectionSetting")]
        public virtual IList<Setting> Settings { get { return _settings; } set { _settings = value; } }
    }

    [ActiveRecord("Setting", Schema = "dbo")]
    internal partial class Setting : ActiveRecordBase<Setting>
    {
        private DateTime _expiresOnUTC;
        private IList<Section> _sections = new List<Section>();
        private int _settingId;
        private string _settingName;
        private string _settingValue;

        public Setting()
        {
        }

        public Setting(string SettingName, string SettingValue, DateTime ExpiresOnUTC)
        {
            _settingName = SettingName;
            _settingValue = SettingValue;
            _expiresOnUTC = ExpiresOnUTC;
        }

        [Property("SettingName", ColumnType = "String", NotNull = true)]
        public virtual string SettingName { get { return _settingName; } set { _settingName = value; } }

        [Property("SettingValue", ColumnType = "String", NotNull = true)]
        public virtual string SettingValue { get { return _settingValue; } set { _settingValue = value; } }

        [Property("ExpiresOnUTC", ColumnType = "Timestamp", NotNull = true)]
        public virtual DateTime ExpiresOnUTC { get { return _expiresOnUTC; } set { _expiresOnUTC = value; } }

        [PrimaryKey(PrimaryKeyType.Identity, "SettingId", ColumnType = "Int32")]
        public virtual int SettingId { get { return _settingId; } set { _settingId = value; } }

        [HasAndBelongsToMany(typeof (Section), ColumnRef = "SectionId", ColumnKey = "SettingId", Schema = "dbo",
            Table = "SectionSetting")]
        public virtual IList<Section> Sections { get { return _sections; } set { _sections = value; } }
    }
}