将字节数组解析为XML

时间:2014-08-11 17:38:28

标签: c# .net xml json parsing

我有一个带有几个命令的串行通信协议,所有命令都在文本文件中指定。我有一个提供基本通信方法的库,但我想添加解析而不必硬编码指定数据包格式的所有结构。根据我的发现,这可能是使用XML或JSON,但我不知道从哪里开始。

我的文本文件中指定的命令格式示例如下:

[255]
Name = Get Relay State
Param1 = Relay Number, uint8_t
Result1 = Relay State, uint8_t

因此,字节数组中此命令获取中继1的请求将为FF 01,其响应类似于FF 00(表示中继已打开)。

这是一个高度简化的例子,因为有数百个这些命令,每个命令都有许多输入和输出。

我是否可以通过某种方式提取值,字段名称和命令名称,而无需为每个命令中的每个输入和输出明确声明struct?到目前为止,我已经完成了几个,但即使使用代码生成工具,协议的更改也不灵活。

1 个答案:

答案 0 :(得分:1)

您可以使用我编写的类来解析您定义的串行协议。我还没有测试过这段代码,所以可能需要进行一些调整才能让它运行起来。这基于我在下面定义的XML模式:

<?xml version="1.0" encoding="utf-8" ?>
<protocol>
  <!-- Get Relay State (255) -->
  <command name="GetRelayState" value="255">
    <parameters>
      <parameter name="RelayNumber" type="Byte"/>
    </parameters>
    <results>
      <result name="RelayState" type="Byte"/>
    </results>
  </command>

  <!-- Get Voltage (254) -->
  <command name="GetVoltage" value="254">
    <parameters>
      <parameter name="CircuitNumber" type="Short"/>
    </parameters>
    <results>
      <result name="Voltage" type="Single"/>
      <result name="MaxVoltage" type="Single"/>
      <result name="MinVoltage" type="Single"/>
    </results>
  </command>
</protocol>

这是C#代码

public class ProtocolManager
{
    public event Action<Command> ResponseReceived;

    // This will read the XML definition of your protocol into a Protocol object
    public Protocol BuildProtocol()
    {
        var protocol = new Protocol
        {
            Commands = new List<Command>()
        };

        var xmlFile = new XmlDocument();
        xmlFile.Load(
            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Protocol.xml"));

        var protocolElement = xmlFile["protocol"];

        foreach (XmlNode command in protocolElement.ChildNodes)
        {
            // Load the command definition
            var comm =
                new Command
                {
                    Name = command.Attributes["name"].Value,
                    Value = int.Parse(command.Attributes["value"].Value),
                    Parameters = new List<Parameter>(),
                    Results = new List<Result>()
                };

            // Load the list of parameters
            foreach (XmlNode p in command.SelectSingleNode("parameters").ChildNodes)
            {
                comm.Parameters.Add(
                    new Parameter
                    {
                        Name = p.Attributes["name"].Value,
                        ParamType = Type.GetType(p.Attributes["type"].Value)
                    });
            }

            // Load the list of expected results
            foreach (XmlNode p in command.SelectSingleNode("results").ChildNodes)
            {
                comm.Parameters.Add(
                    new Parameter
                    {
                        Name = p.Attributes["name"].Value,
                        ParamType = Type.GetType(p.Attributes["type"].Value)
                    });
            }

            protocol.Commands.Add(comm);
        }

        // You should now have a complete protocol which you can bind to UI controls
        // such as dropdown lists, and be able to parse
        return protocol;
    }

    // This will test the incoming stream for responses (packets)
    public void TestProtocol(SerialPort port, Protocol p)
    {
        int bytesRead = 0;
        var buffer = new byte[1024];
        do
        {
            Thread.Sleep(50); // Give the port 50ms to receive a full response
            bytesRead = port.Read(buffer, 0, buffer.Length);

            // For the sake of simplicity assume we read the whole packet in a single read and we don't 
            // need to look for message encapsulation (STX, ETX).
            var commandValue = buffer[0];
            var command = p.Commands.SingleOrDefault(c => c.Value == commandValue);
            if (command == null)
            {
                throw new NotSupportedException(
                    String.Format("Unknown command received {0}", commandValue));
            }
            // Read result parameters
            int index = 1;
            foreach (var r in command.Results)
            {
                // Here you need to implement every data type you are expecting to receive
                // I have done 2
                switch (r.ResultType.Name)
                {
                    case "Byte":
                        r.Value = buffer[index];
                        index++; // Reading only a single byte
                        break;
                    case "Short":
                        r.Value = (float)(buffer[index] >> 8 | buffer[index]);
                        index += 2; // Reading a 16bit short is 2 bytes
                        break;
                    default:
                        throw new NotSupportedException(
                            String.Format("Unknown response type {0}", r.ResultType.Name));
                }
            }

            // Notify listening client that we received a message response
            // and it will contain the response parameters with values
            var evt = ResponseReceived;
            if (evt != null)
            {
                evt(command);
            }

        } while (bytesRead > 0);

        // End of read / wait for next command to be sent
    }
}

// Represents your serial protocol as a list of support commands 
// with request/response parameters
public  class Protocol
{
    public List<Command> Commands { get; set; }

    // Used to data-bind to comboboxes etc.
    public List<string> GetCommandNames()
    {
        return Commands.Select(c => c.Name).OrderBy(c => c).ToList();
    }
}

// A single command with a name, value and list of request/response parameters
public class Command
{
    public string Name { get; set; }
    public int Value { get; set; }

    public List<Parameter> Parameters { get; set; }
    public List<Result> Results { get; set; }
}

public class Parameter
{
    public string Name { get; set; }
    public Type ParamType { get; set; }
    public object Value { get; set; }
}

public class Result
{
    public string Name { get; set; }
    public Type ResultType { get; set; }
    public object Value { get; set; }
}