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.

asked Apr 11, 2014 at 20:28

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; }
 }
}
answered May 17, 2014 at 17:40

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.