如何解析没有明确分隔符的键值字符串?

How do I parse a key-value string without a clear delimiter?

我正在开发一个控制 3D 打印机的小程序。当我向它发送内容时,它通常会以 ok 进行响应,但如果出现问题,它会发送如下内容:

 T:221.0 /220.0 @:0 W:1

如果它有适当的分隔符,我可以很容易地解析它,但是使用 space 是不可靠的,因为字符串 221.0 /220.0。因此,如果我使用 space 作为分隔符,/220.0 可能会被视为键值对,但没有键,因为它在 T 下。我正计划获取每个冒号的索引和它后面的简单开始 1 个字符,但密钥长度也是可变的。例如:

 T:221.0 /220.0 B@:127 @:0 W:1

B@ 现在是两个字符长。

我做了一些研究,但我发现的所有内容都有适当的分隔符,例如 URL with data

http://www.niederschlagsradar.de/images.aspx?jaar=-6&type=europa.cld&datum=201311161500&cultuur=en-GB&continent=europa

我计划获取每个冒号的索引,然后在找到冒号时向后搜索 space 作为起点。同样,下一个键值对的起点将作为前一个键值对的终点。但是,我不确定这是否是正确的方法。

主要问题: 如何解析没有适当定界符的字符串?我真的没有具体要求。无论是数组还是列表,将键和值的变量分开,或者只是将所有内容都推到一个数组中,这样

string[] data = {key1,value1,key2,value2,key3,value3};

更新:这是第二个示例中的键值对:

Key:Value
  T:221.0 /220.0
 B@:127
  @:0
  W:1

更多示例:

 T:221.0 /220.0 B:85.7 /120 B@:30W @:0 W:8

Key:Value
T:221.0 /220.0
B:85.7 /120
B@:30W
@:0
W:8

这是另一个更复杂的:

 T:171.4 /220.0 B:90.3 /120 T1:171.4 /220.0 B@:30 @:12W W:6

Key:Value
T:171.4 /220.0   // Temperature of first nozzle heater
B:90.3 /120      // Temperature of the hot plate it's printing on
T1:171.4 /220.0  // Temperature of the second nozzle heater if it exists
B@:30            // Raw power consumption of hotbed (unit depends on config)
@:12W            // Power of of nozzle in Watts (unit depends on config)
W:6              // Waiting time (in seconds). If the optimal conditions are met and this counts down to zero, printing resumes. Else, reset to 10.

示例字符串开头的 space 是故意的。它确实以 space 开头。对于那些感兴趣的人,这些是 Arduino Mega 运行 Marlin 3D 打印固件的回复。这些是当打印机加热器还不够热而无法挤出时的回复。

相关:How to parse a string to find key-value pairs in it

From each colon position found,
    search backwards until a whitespace character is found
    search forward until a whitespace character is found

我不会将下一个键值对的起点与上一个结束点联系起来,因为键值对之间可能有几个空格。我只会确定从冒号位置开始的键和值。

我会遵循这个逻辑:

  1. 以冒号分隔。
  2. 第一项始终是第一个键。
  3. 最后一项始终是最后一个值。
  4. 对于每个中间项目(从第二个项目开始)检查最后一个 space 的索引。最后一个 space 之前的所有内容都是最新密钥的值,右边的所有内容都是下一个密钥。

代码:

private List<KeyValuePair<string, string>> ParsePrinterResponse(string rawResponse)
{
    List<KeyValuePair<string, string>> pairs = new List<KeyValuePair<string, string>>();
    string[] colonItems = rawResponse.Trim().Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
    if (colonItems.Length > 1)
    {
        string currentKey = colonItems[0], currentValue = "";
        for (int i = 1; i < colonItems.Length; i++)
        {
            string currentItem = colonItems[i];
            int spaceIndex = currentItem.LastIndexOf(" ");
            if (spaceIndex < 0)
            {
                //end of string, whole item is the value
                currentValue = currentItem;
            }
            else
            {
                //middle of string, left part is value, right part is next key
                currentValue = currentItem.Substring(0, spaceIndex);
            }
            pairs.Add(new KeyValuePair<string, string>(currentKey, currentValue));
            currentKey = currentItem.Substring(spaceIndex + 1);
        }
    }
    return pairs;
}

用法示例:

errorBox.Lines = ParsePrinterResponse("T:171.4 /220.0 B:90.3 /120 T1:171.4 /220.0 B@:30 @:12W W:6").ConvertAll(p =>
{
    return string.Format("{0}:{1}", p.Key, p.Value);
}).ToArray();