Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XSLT

Customize Applications with XML Fragments: Part 2

3.00/5 (2 votes)
19 Jun 20073 min read 1   111  
An advanced discussion of customizing applications with XML fragments

In my first article, I described why you should consider using my library. In this second part, I will discuss more advanced topics.

Recursive Structure

First of all, I will examine tree structure management. Each node will have a name, font color and icon.

C#
public class TreeItemCollection : ElementList<TreeItem>
{
}

public class TreeItem : OwnedElement<TreeItemCollection>
{
    private const string ParentName = "parent";
    private TreeItem _parent;
    [Serialization(ParentName)]
    public TreeItem Parent
    {
        get { return _parent; }
        set
        {
            if (_parent != value)
            {
                LogChange(ParentName, _parent, value);
                _parent = value;
            }
        }
    }

    private const string ColorName = "color";
    private Color _color;
    [Serialization(ColorName)]
    public Color Color
    {
        get { return _color; }
        set
        {
            if (_color != value)
            {
                LogChange(ColorName, _color, value);
                _color = value;
            }
        }
    }
    private const string IconName = "icon";
    private string _icon;
    [Serialization(IconName, SerializationSettings.SerializeAsAttribute)]
    public string Icon
    {
        get { return _icon; }
        set
        {
            if (_icon != value)
            {
                LogChange(IconName, _icon, value);
                _icon = value;
            }
        }
    }
}

You can see in the TreeItem class that there is a property called Parent that represents the parent node. With this library, working with recursive structures is quite simple. Take a look at the following listing:

C#
TreeItemCollection collection = new TreeItemCollection();
TreeItem root = collection.Add("root");
root.Color = Color.Red;
root.Icon = "icon1";

TreeItem item1 = collection.Add("item1");
item1.Color = Color.Black;
item1.Icon = "icon2";

TreeItem item2 = collection.Add("item2");
item2.Color = Color.White;
item2.Icon = "icon1";

TreeItem item11 = collection.Add("item11");
item11.Color = Color.Purple;
item11.Parent = item11;
item11.Icon = "icon3";

Writer.Write(collection, "test.xml", false);
collection.AcceptChanges();
item11.Parent = root; 
Writer.Write(collection, "changes.xml", true);

TreeItemCollection collection2 = new TreeItemCollection();
Reader.Read("test.xml", collection2, null);
Reader.Read("changes.xml", collection2, null);
TreeItem rItem = collection2.FindElem(root.ID.Value);
TreeItem new_item11 = collection2.Find(item11.Name);
if (new_item11.Parent == rItem)
    Console.WriteLine("Perfect");

As you see, it is very easy to work with referenced objects. Writing and reading don't require any additional processing. Producing test.xml gives:

XML
<e:TreeItemCollection xmlns:e="elements">
    <e:TreeItem color="-65536" icon="icon1" 
        ID="e457617f-03fd-4948-9f83-304222f3ab0b" Name="root" />
    <e:TreeItem color="-16777216" icon="icon2" 
        ID="85953eeb-b205-482b-9b4c-b6f8156251e9" Name="item1" />
    <e:TreeItem color="-1" icon="icon1" 
        ID="13140ccc-8c5e-4471-ba30-a51426bbd605" Name="item2" />
    <e:TreeItem parent="b141e55a-b043-49be-b2d9-ce669e116362" 
        color="-8388480" icon="icon3" 
        ID="b141e55a-b043-49be-b2d9-ce669e116362" Name="item11" />
</e:TreeItemCollection>

Producing changes.xml gives:

XML
<e:TreeItemCollection xmlns:e="elements" xmlns:o="versioning">
    <e:TreeItem parent="e457617f-03fd-4948-9f83-304222f3ab0b" 
        o:parent="b141e55a-b043-49be-b2d9-ce669e116362" 
        ID="b141e55a-b043-49be-b2d9-ce669e116362" />
</e:TreeItemCollection>

Advantages

  • Simple XML serialization cannot serialize recursive structures, but this library can do it with ease.
  • With DataTable, you can serialize recursive structures, but you should manually resolve the reference by reading.
  • With the Configuration API, you have the same situation as with DataTable.
  • The referenced element IDs are saved in the serialized XML, which makes the serialized form readable.

Multiple Containers

Until now, I have serialized only one container (List). The second project shows how to serialize two containers. In principle, I must have a third container, Project2Space, which contains the other two containers.

Screenshot - project2space.jpg

So, I split the TreeItem class into two classes: TreeItem and Style.

Screenshot - Style.jpg

C#
public class StyleCollection: ElementList<Style>
{
}
    
public class Project2Space: ElementSpace
{
    private StyleCollection _styles = new StyleCollection();
    public StyleCollection Styles
    {
        get { return _styles; }
    }

    private TreeItemCollection _treeItems = new TreeItemCollection();
    public TreeItemCollection TreeItems
    {
        get { return _treeItems; }
    }

    public Project2Space()
    {
        AddInnerElement(_styles);
        AddInnerElement(_treeItems);
    }
}

As you see, the key is to add an inner element. Now you can work with elements as before.

C#
Project2Space space = new Project2Space();

StyleCollection styles = space.Styles;
Style style1 = styles.Add("red icon 1");
style1.Color = Color.Red;
style1.Icon = "icon1";

Style style2 = styles.Add("black icon2");
style2.Color = Color.Black;
style2.Icon = "icon2";

TreeItemCollection collection = space.TreeItems;
TreeItem root = collection.Add("root");
root.Style = style1;

TreeItem item1 = collection.Add("item1");
item1.Style = style2;

TreeItem item2 = collection.Add("item2");
item2.Style = style2;

TreeItem item11 = collection.Add("item11");
item11.Parent = item11;
item11.Style = style1;

Writer.Write(space, "test.xml", false);
space.AcceptChanges();
item11.Parent = root;
item1.Style = style1;
Writer.Write(space, "changes.xml", true);

Project2Space testSpace = new Project2Space();
Reader.Read("test.xml", testSpace, null);
Reader.Read("changes.xml", testSpace, null);
Style redStyle = testSpace.Find(style1.ID.Value) as Style;
TreeItem test_item1 = testSpace.TreeItems.Find(item1.Name);

if (test_item1.Style == redStyle)
    Console.WriteLine("Perfect");

Producing test.xml gives:

XML
<e:Project2Space xmlns:e="elements">
    <e:StyleCollection ID="202142ea-8dbf-4ebe-a2f8-51306bc367bd">
        <e:Style color="-65536" icon="icon1" 
            ID="bd806e02-5d37-457c-a341-862abef580f5" Name="red icon 1" />
        <e:Style color="-16777216" icon="icon2" 
            ID="e88adf6e-63b3-4106-953c-09c7d441fbc3" Name="black icon2" />
    </e:StyleCollection>
    <e:TreeItemCollection ID="280e1811-7bc9-44ed-8c1c-2e492857da12">
        <e:TreeItem style="bd806e02-5d37-457c-a341-862abef580f5" 
            ID="70f42380-6484-4b91-bb4b-3a67de6d5541" Name="root" />
        <e:TreeItem style="e88adf6e-63b3-4106-953c-09c7d441fbc3" 
            ID="5f226519-bb60-4e52-9c09-ea095a3c74d8" Name="item1" />
        <e:TreeItem style="e88adf6e-63b3-4106-953c-09c7d441fbc3" 
            ID="73aa0abb-a1ac-4850-a778-7970cf3a3069" Name="item2" />
        <e:TreeItem parent="ab69de5f-3bcf-4f05-b3e3-0c961b57b0a5" 
            style="bd806e02-5d37-457c-a341-862abef580f5" 
            ID="ab69de5f-3bcf-4f05-b3e3-0c961b57b0a5" Name="item11" />
    </e:TreeItemCollection>
</e:Project2Space>

Producing changes.xml gives:

XML
<e:Project2Space xmlns:e="elements" xmlns:o="versioning">
    <e:TreeItemCollection ID="280e1811-7bc9-44ed-8c1c-2e492857da12">
        <e:TreeItem style="bd806e02-5d37-457c-a341-862abef580f5" 
            o:style="e88adf6e-63b3-4106-953c-09c7d441fbc3" 
            ID="5f226519-bb60-4e52-9c09-ea095a3c74d8" />
        <e:TreeItem parent="70f42380-6484-4b91-bb4b-3a67de6d5541" 
            o:parent="ab69de5f-3bcf-4f05-b3e3-0c961b57b0a5" 
            ID="ab69de5f-3bcf-4f05-b3e3-0c961b57b0a5" />
    </e:TreeItemCollection>
</e:Project2Space>

Nested Structures

In the previous demos, I showed list elements. Sometimes, management of structured data is required, however. So, in the third demo, I will introduce the LogInfo element.

C#
[Serialization("Logging",null,true)]
public class LogInfo : Element
{
    private const string PassswordName = "pswd";
    private string _password;
    [Serialization(PassswordName, 
        SerializationSettings.Encrypt | 
        SerializationSettings.SerializeAsAttribute)]
    public string Password
    {
        get { return _password; }
        set
        {
            if (_password != value)
            {
                LogChange(PassswordName, _password, value);
                _password = value;
            }
        }
    }
    ...
}

In this code, you can see several additional features of the library. First, you can force the writer to encrypt the content of a string property with SerializationSettings.Encrypt. Second, with the [Serialization("Logging",null,true)] attribute, you can prevent the writer from writing the element ID. In this case, I use LogInfo only within the Project2Space element so that I don't need to identify the element by ID. It is very easy to include LogInfo in the configuration.

C#
public class Project2Space: ElementSpace
{
    ...
    private LogInfo _logInfo = new LogInfo();
    public LogInfo LogInfo
    {
        get { return _logInfo; }
    }

    public Project2Space()
    {
        ...
        AddInnerElement(_logInfo);
    }
}

Producing test.xml gives:

XML
<e:Project2Space xmlns:e="elements">
    ...
    <e:Logging pswd="D0wfLkJuLmkoS5F4+X2JGg==" user="username" />
</e:Project2Space>

You can see that the Password property was serialized as a psw attribute and that the content was encrypted.

The Rest

The library also allows you to compress the serialized XML into a valid ZIP.

C#
public static void WriteZip(IElement element, string zipFileName, 
    string fileName, bool writeChanges, string password)
public static void ReadZip(string fileName, IElement element, 
    Logger logger, string password)

Logger collects errors and hints by reading and writing the configuration. Typically, you get the list of unresolved references. Alternatively, if you merge the configuration and the old value from the change log, it doesn't correspond to the value in the instance. If you already have your class hierarchy, you can use the library by implementing the IElement interface.

Screenshot - ielement.jpg

If you don't want to serialize all of the elements, you can implement an IFilter interface.

C#
public interface IFilterElement
{
  bool IsFiltered();
}

If you don't want to serialize some element's property, you can filter it out as follows:

C#
[Serialization("Prop1", "Prop1Filter")]
public string Prop1
...
public bool Prop1Filter

History

  • 19th June, 2007 -- Original version posted

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.