Aloha
Aloha

Reputation: 914

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

I'm whipping up a small program that controls a 3D printer. When I send it something, it usually responds with an ok, but if something's not okay, it sends something like this:

 T:221.0 /220.0 @:0 W:1

I could easily parse it if it had proper delimiters, but using space isn't reliable because of the string 221.0 /220.0. Consequently, if I use space as a delimiter, /220.0 may be treated as a key-value pair but there is no key since it's under T. I was planning on getting the index of every colon and simple start 1 character behind it but the key length is also variable. For example:

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

B@ is now two characters long.

I did some research but all of what I found had proper delimiters like a URL with data.

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

I was planning on getting every colon's index then searching backwards for a space when a colon is found which would serve as a starting point. Likewise, the next key-value pair's start point would serve as an ending for the previous one. But, I'm not sure if it's the right approach.

Main question: How do I parse a string without proper delimiters? I don't really have specific requirements. Be it an array or a list, separate variables for key and value or just shove everything into an array such that

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

Update: Here are the key-value pairs from the second example:

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

Some more samples:

 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

Here's another more complicated one:

 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.

The space at the start of the sample strings are intentional. It does start with a space. For those who are interested, these are the replies of an Arduino Mega running the Marlin 3D printing firmware. These are the replies when the printer heaters aren't hot enough yet to extrude.

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

Upvotes: 1

Views: 715

Answers (2)

Shadow Wizard
Shadow Wizard

Reputation: 66389

I would go with this logic:

  1. Split by the colon character.
  2. First item is always the first key.
  3. Last item is always the last value.
  4. For each middle item (starting from second item) check index of last space. Everything until that last space is the value of the latest key, everything to the right is the next key.

Code:

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;
}

Usage sample:

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();

Upvotes: 1

Guido
Guido

Reputation: 926

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

I would not relate the next key-value pair's starting point to the previous ending one, since there might be several spaces between key-value pairs. I would determine keys and values starting from colon positions only.

Upvotes: 1

Related Questions