Cuberite
A lightweight, fast and extensible game server for Minecraft
NameValueParser.cpp
Go to the documentation of this file.
1 
2 // NameValueParser.cpp
3 
4 // Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
5 
6 #include "Globals.h"
7 #include "NameValueParser.h"
8 
9 
10 
11 
12 
13 // DEBUG: Self-test
14 
15 #if 0
16 
17 class cNameValueParserTest
18 {
19 public:
20  cNameValueParserTest(void)
21  {
22  const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\"";
23 
24  // Now try parsing char-by-char, to debug transitions across datachunk boundaries:
25  cNameValueParser Parser2;
26  for (size_t i = 0; i < sizeof(Data) - 1; i++)
27  {
28  Parser2.Parse(Data + i, 1);
29  }
30  Parser2.Finish();
31 
32  // Parse as a single chunk of data:
33  cNameValueParser Parser(Data, sizeof(Data) - 1);
34 
35  // Use the debugger to inspect the Parser variable
36 
37  // Check that the two parsers have the same content:
38  for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
39  {
40  ASSERT(Parser2[itr->first] == itr->second);
41  } // for itr - Parser[]
42 
43  // Try parsing in 2-char chunks:
44  cNameValueParser Parser3;
45  for (int i = 0; i < sizeof(Data) - 2; i += 2)
46  {
47  Parser3.Parse(Data + i, 2);
48  }
49  if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char
50  {
51  Parser3.Parse(Data + sizeof(Data) - 2, 1);
52  }
53  Parser3.Finish();
54 
55  // Check that the third parser has the same content:
56  for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
57  {
58  ASSERT(Parser3[itr->first] == itr->second);
59  } // for itr - Parser[]
60 
61  printf("cNameValueParserTest done");
62  }
63 } g_Test;
64 
65 #endif
66 
67 
68 
69 
70 
72 // cNameValueParser:
73 
74 cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) :
75  m_State(psKeySpace),
76  m_AllowsKeyOnly(a_AllowsKeyOnly)
77 {
78 }
79 
80 
81 
82 
83 
84 cNameValueParser::cNameValueParser(const char * a_Data, size_t a_Size, bool a_AllowsKeyOnly) :
85  m_State(psKeySpace),
86  m_AllowsKeyOnly(a_AllowsKeyOnly)
87 {
88  Parse(a_Data, a_Size);
89 }
90 
91 
92 
93 
94 
95 void cNameValueParser::Parse(const char * a_Data, size_t a_Size)
96 {
97  ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong!
98 
99  size_t Last = 0;
100  for (size_t i = 0; i < a_Size;)
101  {
102  switch (m_State)
103  {
104  case psInvalid:
105  case psFinished:
106  {
107  return;
108  }
109 
110  case psKeySpace:
111  {
112  // Skip whitespace until a non-whitespace is found, then start the key:
113  while ((i < a_Size) && (a_Data[i] <= ' '))
114  {
115  i++;
116  }
117  if ((i < a_Size) && (a_Data[i] > ' '))
118  {
119  m_State = psKey;
120  Last = i;
121  }
122  break;
123  }
124 
125  case psKey:
126  {
127  // Read the key until whitespace or an equal sign:
128  while (i < a_Size)
129  {
130  if (a_Data[i] == '=')
131  {
132  m_CurrentKey.append(a_Data + Last, i - Last);
133  i++;
134  Last = i;
135  m_State = psEqual;
136  break;
137  }
138  else if (a_Data[i] <= ' ')
139  {
140  m_CurrentKey.append(a_Data + Last, i - Last);
141  i++;
142  Last = i;
144  break;
145  }
146  else if (a_Data[i] == ';')
147  {
148  if (!m_AllowsKeyOnly)
149  {
150  m_State = psInvalid;
151  return;
152  }
153  m_CurrentKey.append(a_Data + Last, i - Last);
154  i++;
155  Last = i;
156  (*this)[m_CurrentKey] = "";
157  m_CurrentKey.clear();
159  break;
160  }
161  else if ((a_Data[i] == '\"') || (a_Data[i] == '\''))
162  {
163  m_State = psInvalid;
164  return;
165  }
166  i++;
167  } // while (i < a_Size)
168  if (i == a_Size)
169  {
170  // Still the key, ran out of data to parse, store the part of the key parsed so far:
171  m_CurrentKey.append(a_Data + Last, a_Size - Last);
172  return;
173  }
174  break;
175  }
176 
177  case psEqualSpace:
178  {
179  // The space before the expected equal sign; the current key is already assigned
180  while (i < a_Size)
181  {
182  if (a_Data[i] == '=')
183  {
184  m_State = psEqual;
185  i++;
186  Last = i;
187  break;
188  }
189  else if (a_Data[i] == ';')
190  {
191  // Key-only
192  if (!m_AllowsKeyOnly)
193  {
194  m_State = psInvalid;
195  return;
196  }
197  i++;
198  Last = i;
199  (*this)[m_CurrentKey] = "";
200  m_CurrentKey.clear();
202  break;
203  }
204  else if (a_Data[i] > ' ')
205  {
206  m_State = psInvalid;
207  return;
208  }
209  i++;
210  } // while (i < a_Size)
211  break;
212  } // case psEqualSpace
213 
214  case psEqual:
215  {
216  // just parsed the equal-sign
217  while (i < a_Size)
218  {
219  if (a_Data[i] == ';')
220  {
221  if (!m_AllowsKeyOnly)
222  {
223  m_State = psInvalid;
224  return;
225  }
226  i++;
227  Last = i;
228  (*this)[m_CurrentKey] = "";
229  m_CurrentKey.clear();
231  break;
232  }
233  else if (a_Data[i] == '\"')
234  {
235  i++;
236  Last = i;
238  break;
239  }
240  else if (a_Data[i] == '\'')
241  {
242  i++;
243  Last = i;
245  break;
246  }
247  else
248  {
249  m_CurrentValue.push_back(a_Data[i]);
250  i++;
251  Last = i;
253  break;
254  }
255  } // while (i < a_Size)
256  break;
257  } // case psEqual
258 
259  case psValueInDQuotes:
260  {
261  while (i < a_Size)
262  {
263  if (a_Data[i] == '\"')
264  {
265  m_CurrentValue.append(a_Data + Last, i - Last);
266  (*this)[m_CurrentKey] = m_CurrentValue;
267  m_CurrentKey.clear();
268  m_CurrentValue.clear();
270  i++;
271  Last = i;
272  break;
273  }
274  i++;
275  } // while (i < a_Size)
276  if (i == a_Size)
277  {
278  m_CurrentValue.append(a_Data + Last, a_Size - Last);
279  }
280  break;
281  } // case psValueInDQuotes
282 
283  case psValueInSQuotes:
284  {
285  while (i < a_Size)
286  {
287  if (a_Data[i] == '\'')
288  {
289  m_CurrentValue.append(a_Data + Last, i - Last);
290  (*this)[m_CurrentKey] = m_CurrentValue;
291  m_CurrentKey.clear();
292  m_CurrentValue.clear();
294  i++;
295  Last = i;
296  break;
297  }
298  i++;
299  } // while (i < a_Size)
300  if (i == a_Size)
301  {
302  m_CurrentValue.append(a_Data + Last, a_Size - Last);
303  }
304  break;
305  } // case psValueInSQuotes
306 
307  case psValueRaw:
308  {
309  while (i < a_Size)
310  {
311  if (a_Data[i] == ';')
312  {
313  m_CurrentValue.append(a_Data + Last, i - Last);
314  (*this)[m_CurrentKey] = m_CurrentValue;
315  m_CurrentKey.clear();
316  m_CurrentValue.clear();
318  i++;
319  Last = i;
320  break;
321  }
322  i++;
323  }
324  if (i == a_Size)
325  {
326  m_CurrentValue.append(a_Data + Last, a_Size - Last);
327  }
328  break;
329  } // case psValueRaw
330 
331  case psAfterValue:
332  {
333  // Between the closing DQuote or SQuote and the terminating semicolon
334  while (i < a_Size)
335  {
336  if (a_Data[i] == ';')
337  {
339  i++;
340  Last = i;
341  break;
342  }
343  else if (a_Data[i] < ' ')
344  {
345  i++;
346  continue;
347  }
348  m_State = psInvalid;
349  return;
350  } // while (i < a_Size)
351  break;
352  }
353  } // switch (m_State)
354  } // for i - a_Data[]
355 }
356 
357 
358 
359 
360 
362 {
363  switch (m_State)
364  {
365  case psInvalid:
366  {
367  return false;
368  }
369  case psFinished:
370  {
371  return true;
372  }
373  case psKey:
374  case psEqualSpace:
375  case psEqual:
376  {
377  if ((m_AllowsKeyOnly) && !m_CurrentKey.empty())
378  {
379  (*this)[m_CurrentKey] = "";
381  return true;
382  }
383  m_State = psInvalid;
384  return false;
385  }
386  case psValueRaw:
387  {
388  (*this)[m_CurrentKey] = m_CurrentValue;
390  return true;
391  }
392  case psValueInDQuotes:
393  case psValueInSQuotes:
394  {
395  // Missing the terminating quotes, this is an error
396  m_State = psInvalid;
397  return false;
398  }
399  case psKeySpace:
400  case psAfterValue:
401  {
403  return true;
404  }
405  }
406  UNREACHABLE("Unsupported name value parser state");
407 }
408 
409 
410 
411 
#define UNREACHABLE(x)
Definition: Globals.h:288
#define ASSERT(x)
Definition: Globals.h:276
bool Finish(void)
Notifies the parser that no more data will be coming.
eState m_State
The current state of the parser.
@ psValueInDQuotes
Just parsed a Double-quote sign after the Equal sign.
@ psEqual
Just parsed the = sign after a name.
@ psFinished
The parser has already been instructed to finish and doesn't expect any more data.
@ psInvalid
The parser has encountered an invalid input; further parsing is skipped.
@ psEqualSpace
Space after m_CurrentKey.
@ psAfterValue
Just finished parsing the value, waiting for semicolon or data end.
@ psKey
Currently adding more chars to the key in m_CurrentKey.
@ psValueRaw
Just parsed a raw value without a quote.
@ psKeySpace
Parsing the space in front of the next key.
@ psValueInSQuotes
Just parsed a Single-quote sign after the Equal sign.
bool m_AllowsKeyOnly
If true, the parser will accept keys without an equal sign and the value.
AString m_CurrentValue
Buffer for the current Value;.
cNameValueParser(bool a_AllowsKeyOnly=true)
Creates an empty parser.
AString m_CurrentKey
Buffer for the current Key.
void Parse(const char *a_Data, size_t a_Size)
Parses the data given.