7

I'm creating a program that allow user define formulas with on 4 basic operation: add, subtract, divide, multiple using XML. Let's take an example: User want to define formula like (a + b) x (c + d). The format of the xml as following:

EDIT I had implement this

EDIT Solve. Many thanks to Yaniv's suggestion. My solution as follow:

<xPlugins>
  <xPlugin>
    <Multiple>
      <Add>
        <Operator>
          <value>1</value>
        </Operator>
        <Operator>
          <value>2</value>
        </Operator>
      </Add>
      <Add>
        <Operator>
          <value>3</value>
        </Operator>
        <Operator>
          <value>4</value>
        </Operator>
      </Add>
    </Multiple>
  </xPlugin>
</xPlugins>

classes

//root element
public class xPlugins
{
    [XmlElement("xPlugin", typeof(xPlugin))]
    public xPlugin[] Plugin { get; set; }
}

public class xPlugin
{
    [XmlElement("Multiple", typeof(Multiple))]
    [XmlElement("Add", typeof(Add))]
    [XmlElement("Subtract", typeof(Divide))]
    [XmlElement("Divide", typeof(Divide))]
    [XmlElement("Operator", typeof(Operand))]
    public Calculator calculator { get; set; }        
}

//Deseirialize ultility
static class readXML
{        
    public static void getObject(ref xPlugins plugins)
    {
        try
        {
            List<Type> type = new List<Type>();
            type.Add(typeof(Add));
            type.Add(typeof(Minus));
            type.Add(typeof(Multiple));
            type.Add(typeof(Subtract));
            type.Add(typeof(Operator));

            XmlSerializer xml = new XmlSerializer(typeof(xPlugin), type.ToArray());

            FileStream fs = new FileStream("test.xml", FileMode.Open);

            plugins = (xPlugins)xml.Deserialize(fs);
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

public abstract class Calculator
{
    [XmlElement("Multiple", typeof(Multiple))]
    [XmlElement("Add", typeof(Add))]
    [XmlElement("Subtract", typeof(Subtract))]
    [XmlElement("Divide", typeof(Divide))]
    [XmlElement("Operator", typeof(Operand))]
    public List<Calculator> calculators{ get; set; }
    public virtual int Calculate()
    {
        return 0;
    }
}

public class Operator : Calculator
{
    public int value { get; set; }

    public Operator() { }

    public override int Calculate()
    {
        return value;
    }
}

public class Add : Calculator
{
    public Add() { }

    public override int Calculate()
    {         
        List<int> value = new List<int>();

        foreach (Calculator calculator in calculators)
        {
            value.Add(calculator.Calculate());
        }

        return value.Sum();
    }
}

public class Minus : Calculator
{
    public Minus() { }

    public override int Calculate()
    {
        int value = calculators[0].Calculate();

        for (int i = 1; i < calculators.Count; i++)
        {
            value -= calculators[i].Calculate();
        }

        return value;
    }
}

public class Divide: Calculator
{ 
    public Divide() { }

    public override int Calculate()
    {
        int value = calculators[0].Calculate();

        for (int i = 1; i < calculators.Count; i++)
        {
            value /= calculators[i].Calculate();
        }

        return value;
    }
}

public class Multiple : Calculator
{
    public Multiple() { }

    public override int Calculate()
    {
        int value = calculators[0].Calculate();

        for (int i = 1; i < calculators.Count; i++)
        {
            value *= calculators[i].Calculate();
        }

        return value;
    }
}

//running test
private void button1_Click(object sender, EventArgs e)
    {
        readXML.getObject(ref this.plugins);

        foreach (Calculator plugin in plugins.calculators)
        {
            plugin.Calculate();
        }
    }

I just have to decorate Calculator property with:

[XmlElement("Multiple", typeof(Multiple))]
[XmlElement("Add", typeof(Add))]
[XmlElement("Subtract", typeof(Divide))]
[XmlElement("Divide", typeof(Divide))]
[XmlElement("Operator", typeof(Operand))]
Doan Cuong
  • 2,594
  • 4
  • 22
  • 39

1 Answers1

6

I am guessing you want to use XmlSerializer. If you need a "polymorphic" deserialization you can pass a list of types that the serializer should know about (this works if they all inherit from the same base class but not from interface).

Example:

List<Type> extraTypes = new List<Type>();
extraTypes.Add(typeof(multiple));
extraTypes.Add(typeof(add));
extraTypes.Add(typeof(substract));
extraTypes.Add(typeof(divide));
var ser = new XmlSerializer(typeof(Foo), extraTypes.ToArray());

It's explained here: Serializing and restoring an unknown class

But there is another problem that in your XML your operand can hold two different types: an operation or an parameter (a, b, c, d) and you cannot represent it in your class.

Something that I usually see is this (I implemented only the add operation, and I am assuming the expression is numeric):

public class Expression
{
  public virtual int Evaluate()
  {
  }
}

public class Add : Expression
{
  Expression _left;
  Expression _right;

  public Add(Expression left, Expression right)
  {
    _left = left;
    _right = right;
  }

  override int Evalute()
  {
    return _left.Evalute() + _right.Evalute();
  }
}

public class Parameter : Expression
{
  public int Value{get;set;}

  public Parameter(string name)
  {
    // Use the name however you need.
  }

  override int Evalute()
  {
    return Value;
  }
}

This way you have only one base class so everything is simpler. If that make sense I guess it won't be hard to deserialize it.

EDIT: If the base class is Calculator (instead of Expression) the XML will look like this:

<Calculator xsi:type="Multiple">
  <calculators>
    <Calculator xsi:type="Add">
      <calculators>
        <Calculator xsi:type="Operator">
          <value>12</value>
        </Calculator>
      </calculators>
    </Calculator>
  </calculators>
</Calculator>

I have created a simple calculator object and serialized it and that's what I got. If you will deserialize it you will get a calculator that will return 12.

Maybe you can use XmlAttributes to change the names of the elements in the XML or in the worst case write your own deserializer.

Community
  • 1
  • 1
Yaniv
  • 104
  • 7
  • Your implementation really save my day. But there is something I still confuse so I would really appreciate if you would spend some of your precious time explain it. First: what is the meaning of `name` paremeter in `Paremeter` construction? What is it suppose to be when I deserialize my XML? (name of operation like "add" | "subtract" | "multiple" | "divide") Second: Must I pass type of `Parameter` class to `XmlSerializer` too? Once again, thanks so much for your response and have a nice day, Sir! – Doan Cuong Jun 25 '13 at 18:28
  • The parameter class (maybe the name isn't that good) is for the simple operands (e.g. a, b , c, d). I am not sure how you are going to use it. Where do you get the values of the operands (a = 10)? You should add the parameter class to the extraTypes. I will edit my answer to reflect it. – Yaniv Jun 26 '13 at 07:15
  • Thanks for your suggested, I'll give it a try and will feedback to you ASAP. Have a nice day, Sir! – Doan Cuong Jun 26 '13 at 08:03
  • I had edited my question. I would appreciate if you would take some time to review it. – Doan Cuong Jun 26 '13 at 08:45
  • I have added the XML that was serialized using your code to my answer – Yaniv Jun 26 '13 at 12:27
  • Thanks very much, Sir! Base on your suggestion, I found out the solution for my case. You can take a look at my question to see it if you would like to. Once again, thank you! – Doan Cuong Jun 27 '13 at 07:02