0

For an API I'm building what it does is load an XML file that is passed to it as a string, drill to the correct element, modify values, and return the modified XML.

I'm doing something like this in my code

if (valid)
{
    XDocument docRoot = XDocument.Parse(XMLstring);

    var node1 = docRoot.Descendants(namespace + @"node1");

    foreach (var result in node1) 
    {
        var child1 = result.Descendants(ns + "child1");

        var test = child1.First();
        test.SetValue(Transform(test.Value));
    }


    //return the transformed XML

}
else { }

Now I want to create a configuration file in XML format that allows user to specify which nodes in the XML to modify and what modification to perform, (later I want to make use of this in a GUI)

so something like

<root Value="">
    <Modify Xpath="\parent\child1\superchild\" Attribute="" Mode="delete" />
    <Modify Xpath="\parent\child2\" Attribute="Value" Mode="multiply" />
</root>

And I have these two pieces but don't know how to connect them together. I think I have to make a class from my XML configuration file and somehow map those to the values in the other XML.

1 Answer 1

1

I have created a possible solution by leveraging the standard configuration classes from .Net and some common patterns to achieve what you want.

Based on your sample XML and configuration I assume you want to perform on action (Mode) on element (Xpath) or an attribute (Attribute) on a given xml.

To make this possible I created an Interface that takes the element and the attribute to perform one single action. This can be implemented with a command pattern.

The correct implementation class is selected by using a factory pattern. By providing a Register method you enable users of your api to provide implementation of actions you didn't provide. You only share an common interface and the Factory class. The rest of the implementation is hidden.
Do notice that in this example there is only one instance of each command that is reused. If you require a per call instance the factory needs to be extended and would require concretetypes to be registered.

The main program would look this:

static void Main()
{
    // get the config
    var root = (Root) ConfigurationManager.GetSection("root");

    // your input xml
    var docRoot = XDocument.Parse(@"<parent><child1><superchild></superchild></child1><child2 Value=""3""></child2></parent>");

    // loop over all actions that need to be performed
    foreach (ModifyElement modact in root.ModifyActions)
    {
        var nodes = docRoot.XPathSelectElements(modact.Xpath);

        // create the correct command class based on the value of Mode
        var cmd = ModifyCommandFactory.GetInstance(modact.Mode);

        foreach (var node in nodes)
        {
            // if we have an attribute, try to grab it
            XAttribute attr = String.IsNullOrEmpty(modact.Attribute)?
               null:node.Attributes(modact.Attribute).SingleOrDefault();                        
            // now execute our command
            cmd.Execute(node, attr);
        }
    }

    var sb = new StringBuilder();
    var sw = new XmlTextWriter(new StringWriter(sb));    
    docRoot.WriteTo(sw);
    sw.Close();
    Debug.WriteLine(sb);
}

The slightly adapted app.config file to hold your Modify commands

<configuration>

  <configSections>
    <section name="root" type="WindowsFormsApplication1.Root, WindowsFormsApplication1"/>
  </configSections>

  <root Value="">
    <actions>
        <Modify Xpath="/parent/child1/superchild" Attribute="" Mode="delete" />
        <Modify Xpath="/parent/child2" Attribute="Value" Mode="multiply" />
    </actions>
  </root>

</configuration>

To read your config values a pretty basic implementation of the configuration classes is needed for the ConfigSection, ConfigCollection and ConfigElement. You find the code needed at the end of this post.

Interface

The common interface has an Execite method that takes an XElement and an XAttribute

public interface IModifyCommand
{
    void Execute(XElement element, XAttribute attribute);
}

Commands

For each mode we implement a ConcreteCommand that implements IModifyCommand

Multiply

public class Multiply:IModifyCommand
{
    public void Execute(XElement element, XAttribute attribute)
    {
        int value;
        if (attribute != null &&
            Int32.TryParse(attribute.Value, out value))
        {
            // this can break due to oveflow!
            value *= value;
            attribute.Value = value.ToString(CultureInfo.InvariantCulture);
        }
        if (element != null &&
            Int32.TryParse(element.Value, out value))
        {
            // this can break due to oveflow!
            value *= value;
            element.Value = value.ToString(CultureInfo.InvariantCulture);
        }

    }
}

DeleteCommand

public class Delete:IModifyCommand
{
    public void Execute(XElement element, XAttribute attribute)
    {
        if (attribute != null)
        {
            attribute.Remove();
        }
        if (element != null)
        {
            element.Remove();
        }
    }
}

NoopCommand

To make our life easier we also have a NoopCommand for the case when no suitable Mode is defined in the config.

public class Noop:IModifyCommand
{
    public void Execute(XElement element, XAttribute attribute)
    {
        // do nothing
    }
}

Factory to provide instances for several modes

public static class ModifyCommandFactory
{
    static readonly Dictionary<string,IModifyCommand> commands = new Dictionary<string, IModifyCommand>();
    static ModifyCommandFactory()
    {
        // our well known commands
        Register("delete", new Delete());
        Register("multiply", new Multiply());
    }

    internal static IModifyCommand GetInstance(string key)
    {
        IModifyCommand cmd;
        if (!commands.TryGetValue(key, out cmd))
        {
            cmd = new Noop(); // nothing found, so use the No-operation command
        }
        return cmd;
     }

     // will be used by users to provide their own commands 
     public static void Register(string mode, IModifyCommand modifycommand)
     {
         commands.Add(mode, modifycommand);
     }
}

ConfigSection

public class Root:ConfigurationSection
{
    private const string ValueKey = "Value";
    private const string ActionsKey = "actions";

    [ConfigurationProperty(ValueKey,
         IsRequired = true,
         IsKey = false)]
    public string Value
    {
        get { return (string)this[ValueKey];}
        set { this[ValueKey] = value; }
    }

    [ConfigurationProperty(ActionsKey,
        IsRequired = true,
        IsKey = false)]
    public ModifyCollection ModifyActions
    {
        get { return (ModifyCollection)this[ActionsKey]; }
        set { this[ActionsKey] = value; }
    }
}

ConfigCollection

public class ModifyCollection:ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new ModifyElement();
    }

    public override ConfigurationElementCollectionType CollectionType
    {
        get{ return ConfigurationElementCollectionType.BasicMap;}
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        var me = (ModifyElement) element;
        return me.Xpath + me.Attribute + me.Mode;
    }

    protected override string ElementName
    {
        get { return "Modify"; }
    }
}

ConfigElement

public class ModifyElement:ConfigurationElement
{
    private const string XPathKey = "Xpath";
    private const string AttributeKey = "Attribute";
    private const string ModeKey = "Mode";

    [ConfigurationProperty(XPathKey,
         IsRequired = true,
         IsKey = false)]
    public string Xpath
    {
        get { return (string)this[XPathKey]; }
        set { this[XPathKey] = value; }
    }

    [ConfigurationProperty(AttributeKey,
         IsRequired = true,
         IsKey = false)]
    public string Attribute
    {
        get { return (string)this[AttributeKey]; }
        set { this[AttributeKey] = value; }
    }

    [ConfigurationProperty(ModeKey,
         IsRequired = true,
         IsKey = false)]
    public string Mode
    {
        get { return (string)this[ModeKey]; }
        set { this[ModeKey] = value; }
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.