Click here to Skip to main content
15,896,118 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="Test.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.02.16�>Initial version</change>
// <history>

using System;
using System.Collections.Specialized;
using System.Drawing;
using System.IO;
using System.Collections; 
using System.Diagnostics;
using MetaPropeller.UnitTest;

namespace MetaPropeller.Configuration
{
  public class Test
  {
    // Test enumeration used in conversion tests
    enum Digits { Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine };

    private static TreeConfiguration CreateTreeConfigurationInstance(bool deleteExistingFile)
    {
      string configFilePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + 
        Path.DirectorySeparatorChar + "TreeConfigurationTest.cfg";
      TreeConfiguration cfg = null;
      try
      {
        if (deleteExistingFile && File.Exists(configFilePath))
          File.Delete(configFilePath);
        cfg = new XmlConfiguration(configFilePath, "MyApplication");
      }
      catch (Exception)
      {
      }
      return cfg;
    }

    [TestMethod]
    public static void TestPathSyntax()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);  // true: delete existing file
      
      cfg["/node1/node2/key1"] = "1";
      cfg["node1/node2/key2"] = "2";
      cfg["rootkey1"] = "root1";
      cfg["/rootkey2"] = "root2";

      Debug.Assert((string) cfg["node1.node2.key1"] == "1");
      Debug.Assert((string) cfg[".node1.node2.key1"] == "1");
      Debug.Assert((string) cfg[@"\node1\node2\key1"] == "1");
      Debug.Assert((string) cfg[@"node1\node2\key1"] == "1");
      Debug.Assert((string) cfg[@"\node1/node2.key1"] == "1");

      Debug.Assert((string) cfg[@"\node1\node2\key2"] == "2");
      Debug.Assert((string) cfg[@"node1.node2.key2"] == "2");

      Debug.Assert((string) cfg["rootkey1"] == "root1");
      Debug.Assert((string) cfg["/rootkey1"] == "root1");
      Debug.Assert((string) cfg[@"\rootkey2"] == "root2");
      Debug.Assert((string) cfg["rootkey2"] == "root2");
    }

    [TestMethod]
    public static void TestSetGetValue()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true); 

      // Set a few values
      cfg["/node/StringKey"] = "abc"; 
      cfg["/node/IntKey"] = 123; 
      cfg["/node/DoubleKey"] = 123.45678; 
      cfg["/node/BooleanKey"] = true; 
      DateTime now = DateTime.Now;
      cfg["/node/DateTimeKey"] = now;
      cfg["/node/EnumKey"] = Digits.Eight;

      // Check those values
      Debug.Assert((string) cfg["/node/StringKey"] == "abc");
      Debug.Assert((int) cfg["/node/IntKey"] == 123);
      Debug.Assert((double) cfg["/node/DoubleKey"] == 123.45678);
      Debug.Assert((bool) cfg["/node/BooleanKey"] == true);
      Debug.Assert((DateTime) cfg["/node/DateTimeKey"] == now);
      Debug.Assert((Digits) cfg["/node/EnumKey"] == Digits.Eight);
    }

    [TestMethod]  
    public static void TestPathCaseInsensitivity() 
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      cfg["/Node1/Node2/Key"] = "abc"; 
      Debug.Assert((string) cfg["/noDe1/NODE2/keY"] == "abc");
    }

    [TestMethod]  
    public static void TestUnknownKey() 
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);
      Debug.Assert(cfg["/NonExistingNode/ThisKeyShouldNotExist"] == null); // null: invalid path
      // Try to remove an unknown key
      cfg["/NonExistingNode/ThisKeyShouldNotExist"] = null;
    }

    [TestMethod]
    public static void TestEnumerateKeys()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      // Test enumeration of a non-existing node (iterate over an empty collection)
      foreach (string key in cfg.Keys["/NonExistingNode1/NonExistingNode2"])
      {
        Debug.Assert(false);
      }
      // Make a test hierarchy
      cfg["/node1/node2/key1"] = "abcdef";
      cfg["/node1/node2/key2"] = 123;
      cfg["/node1/node2/key3"] = true;

      // Enumerate keys of node node2
      ICollection keysCollection = cfg.Keys["/node1/node2"];
      Debug.Assert(keysCollection != null);
      Debug.Assert(keysCollection.Count == 3);

      string[] keys = new string[keysCollection.Count];
      keysCollection.CopyTo(keys, 0);

      // The order of Keys is not guaranteed in any way 
      // so we can only test for a presence of a certain key here
      Debug.Assert(Array.IndexOf(keys, "key1") != -1);
      Debug.Assert(Array.IndexOf(keys, "key2") != -1);
      Debug.Assert(Array.IndexOf(keys, "key3") != -1);
    }

    [TestMethod]
    public static void TestEnumerateValues()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      // Test enumeration of a non-existing node (iterate over an empty collection)
      foreach (object val in cfg.Values["/NonExistingNode1/NonExistingNode2"])
      {
        Debug.Assert(false);
      }
      // Make a test hierarchy
      cfg["/node1/node2/key1"] = "abcdef";
      cfg["/node1/node2/key2"] = 123;
      DateTime now = DateTime.Now;
      cfg["/node1/node2/key3"] = now; // set value

      // Enumerate values of node node2
      ICollection valuesCollection = cfg.Values["/node1/node2"];
      Debug.Assert(valuesCollection != null);
      Debug.Assert(valuesCollection.Count == 3);
      
      object[] values = new object[valuesCollection.Count];
      valuesCollection.CopyTo(values, 0);
      
      // The order of Values in is not guaranteed in any way 
      // (but matches the order returned in Keys)
      // so we can only test for a presence of a concrete value here
      Debug.Assert(Array.IndexOf(values, "abcdef") != -1);
      Debug.Assert(Array.IndexOf(values, 123) != -1);
      Debug.Assert(Array.IndexOf(values, now) != -1);
    }

    [TestMethod]
    public static void TestEnumerateSubNodes()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      // Test enumeration of a non-existing node (iterate over an empty collection)
      foreach (ConfigurationNode n in cfg.Nodes["/NonExistingNode1/NonExistingNode2"])
      {
        Debug.Assert(false);
      }
      // Make a test hierarchy
      cfg["/node1/node2/key1"] = "abc";
      cfg["/node1/node2/key2"] = 123;
      cfg["/node1/node3/key1"] = DateTime.Now; 
      cfg["/node1/node4/node5/node6/key1"] = true;

      ICollection subNodesCollection = cfg.Nodes["/node1"];
      ConfigurationNode[] subNodes = new ConfigurationNode[subNodesCollection.Count];
      subNodesCollection.CopyTo(subNodes, 0);
      Debug.Assert(subNodes.Length == 3);		// node2, node3 and node4

      StringCollection nodesSeenSoFar = new StringCollection();
      foreach (ConfigurationNode n in subNodes)
      {
        Debug.Assert(n.Name == "node2" || n.Name == "node3" || n.Name == "node4");
        Debug.Assert(nodesSeenSoFar.Contains(n.Name) == false);
        nodesSeenSoFar.Add(n.Name);
      }
    }

    [TestMethod]
    public static void TestDynamicStructure()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      cfg["node1/key1"] = "abc";
      foreach (ConfigurationNode n in cfg.Nodes["/"])
      {
        if (n.Name == "node1")
        {
          ConfigurationPath path = new ConfigurationPath("node2/node3");
          ConfigurationNode subNode = n.CreateSubNode(path);
          Debug.Assert(cfg.FindNode("/node1/node2/node3") != null);
        }
      }
      cfg.Save();

    }

    [TestMethod]
    public static void TestFindNode()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      cfg["/node1/node2/node3/node4/key1"] = "find"; 
      ConfigurationNode foundNode = cfg.FindNode("/node1/node2/node3");	 
      Debug.Assert(foundNode != null);
      foundNode = cfg.FindNode("/node1/node2/node3/node4");	 
      Debug.Assert(foundNode != null);
      foundNode = cfg.FindNode("/node5/node6/node7/node8");	 
      Debug.Assert(foundNode == null);
    }

    [TestMethod]
    public static void TestRemoveNode()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      cfg["/node1/node2/node3/node4/key1"] = "delete me"; 
      Debug.Assert(cfg["/node1/node2/node3/node4/key1"] != null); 

      bool removed = cfg.RemoveNode("/node1/node2/node3");
      Debug.Assert(removed);
      Debug.Assert(cfg["/node1/node2/node3/node4/key1"] == null); 
      Debug.Assert(cfg["/node1/node2/node3/node4"] == null); 
			
      ConfigurationNode n = cfg.FindNode("/node1/node2");
      Debug.Assert(n != null);
      Debug.Assert(n.HasSubNodes == false);
    }

    [TestMethod]
    public static void TestRemoveKey()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      // Make a test hierarchy
      cfg["/node1/node2/node3/node4/key1"] = "One"; 
      cfg["/node1/node2/node3/node4/key2"] = 2; 
      cfg["/node1/node2/node3/node4/key3"] = false; 

      // Remove key key2
      cfg["/node1/node2/node3/node4/key2"] = null;  
      
      ICollection keys = cfg.Keys["/node1/node2/node3/node4"];
      Debug.Assert(keys.Count == 2);
      Debug.Assert((string)cfg["/node1/node2/node3/node4/key1"] == "One"); 
      Debug.Assert(cfg["/node1/node2/node3/node4/key2"] == null); 
      Debug.Assert((bool)cfg["/node1/node2/node3/node4/key3"] == false); 
			
      ConfigurationNode n = cfg.FindNode("/node1/node2/node3/node4");
      Debug.Assert(n != null);
      Debug.Assert(!n.Empty);
      Debug.Assert(n.HasKeys);

      // Remove remaining keys
      cfg["/node1/node2/node3/node4/key1"] = null;  
      cfg["/node1/node2/node3/node4/key3"] = null;  
      Debug.Assert(n.Empty);
      Debug.Assert(n.HasKeys == false);
    }

    [TestMethod]
    public static void TestClear()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      // Make a test hierarchy
      cfg["/node1/node2/node3/node4/key1"] = "One"; 
      cfg["/node1/node2/node3/node4/key2"] = 2; 
      cfg["/node1/node2/node3/node4/key3"] = true; 

      cfg["/node1/node2/key1"] = "abcdef";
      cfg["/node1/node2/key2"] = 123;

      cfg["/node2/node3/key1"] = DateTime.Now; 
      cfg["/node3/node5/node6/node7/key1"] = "hi!"; 

      cfg["/rootkey1"] = 1;
      cfg["/rootkey2"] = "two";

      ICollection nodes = cfg.Nodes["/"];
      ICollection keys = cfg.Keys["/"];
      ICollection values = cfg.Values["/"];
      Debug.Assert(nodes.Count == 3);     // node1, node2 and node3
      Debug.Assert(keys.Count == 2);      // rootkey1 and rootkey2
      Debug.Assert(values.Count == 2);    // 1 and "two"

      cfg.Clear();

      nodes = cfg.Nodes["/"];
      keys = cfg.Keys["/"];
      values = cfg.Values["/"];
      Debug.Assert(nodes.Count == 0);
      Debug.Assert(keys.Count == 0);
      Debug.Assert(values.Count == 0);
    }

    [TestMethod]
    public static void TestCustomTypeConverter()
    {
      XmlConfiguration cfg = (XmlConfiguration) CreateTreeConfigurationInstance(true);
            
      MetaPropeller.CustomTypeConverters.MyClass mc = new MetaPropeller.CustomTypeConverters.MyClass("text", 123, DateTime.Now);
      cfg["/MyClassSetting"] = mc;

      bool saved = cfg.Save();
      Debug.Assert(saved);

      cfg = (XmlConfiguration) CreateTreeConfigurationInstance(false); // false: use the existing file (the one we just saved)

      bool loaded = cfg.Load();
      Debug.Assert(loaded);

      MetaPropeller.CustomTypeConverters.MyClass mc2 = (MetaPropeller.CustomTypeConverters.MyClass) cfg["/MyClassSetting"];
      Debug.Assert(mc.Text == mc2.Text);
      Debug.Assert(mc.Number == mc2.Number);
      Debug.Assert(mc.Date.Date == mc2.Date.Date);  
      Debug.Assert(mc.Date.Hour == mc2.Date.Hour && mc.Date.Minute == mc2.Date.Minute); // only parts up to minute are maintained
    }

    [TestMethod]
    public static void TestLoadSave()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      // Make a test hierarchy
      cfg["rootkey1"] = "root1";
      cfg["/rootkey2"] = "root2";
      cfg["/node1/node2/key1"] = "abc";
      cfg["/node1/node2/key2"] = 123;
      DateTime savedDate = DateTime.Now;
      cfg["/node1/node2/key3"] = savedDate; 
      cfg["/node1/node3/key1"] = true; 
      cfg["/node1/node4/node5/node6/key1"] = "hi!"; 
      cfg["/node1/node4/node7/node8/key1"] = 123.4567;
      cfg["/node9/key1"] = Digits.Eight;
      cfg["/Colors/Foreground"] = System.Drawing.SystemColors.WindowText;
      Font f = new Font("Tahoma", 12, FontStyle.Bold);
      cfg["/Font"] = f;

      bool saved = cfg.Save();
      Debug.Assert(saved);

      cfg = CreateTreeConfigurationInstance(false); // false: use the existing file (the one we just saved)
      bool loaded = cfg.Load();
      Debug.Assert(loaded);

      // Repeat above checks on a freshly loaded cfg
      Debug.Assert((string)cfg["rootkey1"] == "root1");
      Debug.Assert((string)cfg["/rootkey2"] == "root2");
      Debug.Assert((string)cfg["/node1/node2/key1"] == "abc");
      Debug.Assert((int)cfg["/node1/node2/key2"] == 123);
      DateTime loadedDate = (DateTime) cfg["/node1/node2/key3"];
      // Since we didn't provide custom type converter for DateTime, default conversion
      // was used. That one constructs loadedDate from a string that contains 
      // all date parts up to seconds. Milliseconds get some random value, so loadedDate
      // is not entirely equal to savedDate. The following three asserts prove that.
      Debug.Assert(savedDate.Ticks != loadedDate.Ticks); 
      Debug.Assert(loadedDate.Date == savedDate.Date);  
      Debug.Assert(loadedDate.Hour == savedDate.Hour && loadedDate.Minute == savedDate.Minute);    

      ICollection keys = cfg.Keys["/node1/node2"];
      Debug.Assert(keys.Count == 3);
      ICollection nodes = cfg.Nodes["/node1"];
      Debug.Assert(nodes.Count == 3); 
      Debug.Assert((bool)cfg["/node1/node3/key1"] == true);
      Debug.Assert((string)cfg["/node1/node4/node5/node6/key1"] == "hi!");
      Debug.Assert((double)cfg["/node1/node4/node7/node8/key1"] == 123.4567);
      Debug.Assert((Digits) cfg["/node9/key1"] == Digits.Eight);
      Debug.Assert((System.Drawing.Color) cfg["/Colors/Foreground"] == System.Drawing.SystemColors.WindowText);
      Font f2 = (Font) cfg["/Font"];
      Debug.Assert(f2.Name == f.Name && f2.Size == f.Size && f2.Style == f.Style);
    }

    [TestMethod]
    public static void TestCulture()
    {
      TreeConfiguration cfg = CreateTreeConfigurationInstance(true);

      float number = 123.456F;
      DateTime now = DateTime.Now;
      // Set the French culture for the whole configuration
      cfg.Culture = "fr-FR";
      ConfigurationNode rootNode = cfg.FindNode("/");	 
      Debug.Assert(rootNode != null);
      cfg["french/number"] = number;  // key in the root node, same as "/number"
      cfg["french/date"] = now; 
      // Override the culture info for a child node
      ConfigurationPath path = new ConfigurationPath("southafrican");
      ConfigurationNode subNode = rootNode.CreateSubNode(path);
      subNode["number"] = number;
      subNode["date"] = now;
      subNode.Culture = "af-ZA";  // (Afrikaans - South Africa). Culture can be set at any time (before/after setting the values)

      bool saved = cfg.Save();
      Debug.Assert(saved);

      cfg = CreateTreeConfigurationInstance(false); // false: use the existing file (the one we just saved)
      bool loaded = cfg.Load();
      Debug.Assert(loaded);

      // Check a freshly loaded cfg
      // French info first
      Debug.Assert((float)cfg["/french/number"] == number);
      DateTime loadedDate = (DateTime) cfg["/french/date"];
      // Since we didn't provide custom type converter for DateTime, default conversion
      // was used. That one constructs loadedDate from a string that contains 
      // all date parts up to seconds. Milliseconds get some random value, so loadedDate
      // is not entirely equal to savedDate. The following three asserts prove that.
      Debug.Assert(now.Ticks != loadedDate.Ticks); 
      Debug.Assert(loadedDate.Date == now.Date);  
      Debug.Assert(loadedDate.Hour == now.Hour && loadedDate.Minute == now.Minute);    

      // South African part
      Debug.Assert((float)cfg["/southafrican/number"] == number);
      loadedDate = (DateTime) cfg["/southafrican/date"];
      Debug.Assert(now.Ticks != loadedDate.Ticks); 
      Debug.Assert(loadedDate.Date == now.Date);  
      Debug.Assert(loadedDate.Hour == now.Hour && loadedDate.Minute == now.Minute);    
    }
  }
}

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