Skip to content

Commit 2dbe477

Browse files
committed
added proper bool support
1 parent 60ef7be commit 2dbe477

File tree

2 files changed

+156
-49
lines changed

2 files changed

+156
-49
lines changed

src/ArduinoYaml.cpp

Lines changed: 128 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,71 @@
3030

3131
#include "ArduinoYaml.hpp"
3232

33+
34+
String _indent_str;
35+
const char* indent( size_t size )
36+
{
37+
_indent_str = "";
38+
for( size_t i=0;i<size;i++ ) _indent_str += " ";
39+
return _indent_str.c_str();
40+
}
41+
42+
43+
44+
// YAML is very inclusive with booleans :-)
45+
// https://yaml.org/type/bool.html
46+
47+
48+
bool string_has_truthy_value( String &_scalar )
49+
{
50+
return _scalar == "y" || _scalar == "Y" || _scalar == "yes" || _scalar == "Yes" || _scalar == "YES"
51+
|| _scalar == "true" || _scalar == "True" || _scalar == "TRUE"
52+
|| _scalar == "on" || _scalar == "On" || _scalar == "ON";
53+
}
54+
55+
56+
bool string_has_falsy_value( String &_scalar )
57+
{
58+
return _scalar == "n" || _scalar == "N" || _scalar == "no" || _scalar == "No" || _scalar == "NO"
59+
|| _scalar == "false" || _scalar == "False" || _scalar == "FALSE"
60+
|| _scalar == "off" || _scalar == "Off" || _scalar == "OFF";
61+
}
62+
63+
64+
bool string_has_bool_value( String &_scalar, bool *value_out )
65+
{
66+
bool has_truthy = string_has_truthy_value(_scalar);
67+
bool has_falsy = string_has_falsy_value(_scalar);
68+
if( has_truthy || has_falsy ) {
69+
*value_out = has_truthy;
70+
return true;
71+
}
72+
return false;
73+
}
74+
75+
76+
bool yaml_node_is_bool( yaml_node_t * yamlNode, bool *value_out )
77+
{
78+
switch (yamlNode->data.scalar.style) {
79+
case YAML_PLAIN_SCALAR_STYLE:
80+
{
81+
String _scalar = String( (char*)yamlNode->data.scalar.value );
82+
if( string_has_bool_value(_scalar, value_out) ) {
83+
return true;
84+
}
85+
}
86+
break;
87+
case YAML_SINGLE_QUOTED_SCALAR_STYLE: /*YAML_LOG_e(" '")*/; break;
88+
case YAML_DOUBLE_QUOTED_SCALAR_STYLE: /*YAML_LOG_e(" \"")*/; break;
89+
case YAML_LITERAL_SCALAR_STYLE: /*YAML_LOG_e(" |")*/; break;
90+
case YAML_FOLDED_SCALAR_STYLE: /*YAML_LOG_e(" >")*/; break;
91+
case YAML_ANY_SCALAR_STYLE: /*abort(); */ break;
92+
}
93+
return false;
94+
95+
}
96+
97+
3398
// used for internals, mainly because yaml_emitter_dump() destroys data after emitting
3499
int yaml_copy_document(yaml_document_t *dest, yaml_document_t *src)
35100
{
@@ -109,9 +174,7 @@ YAMLParser::~YAMLParser()
109174
{
110175
yaml_parser_delete(&_parser);
111176
yaml_emitter_delete(&_emitter);
112-
#if !defined ESP8266 // TODO: fix mem leak with esp8266
113-
yaml_document_delete(&_document);
114-
#endif
177+
yaml_document_delete(&_document);
115178
}
116179

117180

@@ -163,7 +226,9 @@ void YAMLParser::loadDocument()
163226
{
164227
yaml_document_t document;
165228
assert(yaml_emitter_initialize(&_emitter));
166-
yaml_stream_handler_data_t shd = { &_yaml_stream, &_bytes_written };
229+
yaml_stream_handler_data_t shd = { _yaml_stream, &_bytes_written };
230+
yaml_emitter_set_canonical(&_emitter, 1);
231+
yaml_emitter_set_unicode(&_emitter, 1);
167232
yaml_emitter_set_output(&_emitter, &_yaml_stream_writer, &shd);
168233
yaml_emitter_open(&_emitter);
169234
if (!yaml_parser_load(&_parser, &document)) {
@@ -174,8 +239,10 @@ void YAMLParser::loadDocument()
174239
assert( yaml_copy_document(&_document, &document) ); // copy into local document for later parsing
175240
assert( yaml_emitter_dump(&_emitter, &document) ); // dump to emitter for output length evaluation
176241
_emitter_delete:
242+
yaml_parser_delete(&_parser);
177243
yaml_emitter_close(&_emitter);
178244
yaml_emitter_delete(&_emitter);
245+
yaml_document_delete(&document);
179246
}
180247

181248

@@ -219,6 +286,7 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
219286

220287

221288

289+
222290
#if defined HAS_ARDUINOJSON
223291

224292

@@ -233,19 +301,26 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
233301
char* end;
234302
scalar = (char *)yamlNode->data.scalar.value;
235303
number = strtod(scalar, &end);
304+
bool is_bool = false;
305+
bool bool_value = false;
236306
bool is_string = (end == scalar || *end);
307+
if( is_string && yaml_node_is_bool( yamlNode, &bool_value ) ) {
308+
is_bool = true;
309+
}
237310
switch( nt ) {
238311
case YAMLParser::SEQ_KEY:
239312
{
240313
JsonArray array = jsonNode[nodename];
241-
if(is_string) array.add( scalar );
242-
else array.add( number );
314+
if(is_bool) array.add( bool_value );
315+
else if(is_string) array.add( scalar );
316+
else array.add( number );
243317
//YAML_LOG_d("[SEQ][%s][%d] => %s(%s)", nodename, array.size()-1, is_string?"string":"number", is_string?scalar:String(number).c_str() );
244318
}
245319
break;
246320
case YAMLParser::MAP_KEY:
247-
if(is_string) jsonNode[nodename] = scalar;
248-
else jsonNode[nodename] = number;
321+
if(is_bool) jsonNode[nodename] = bool_value;
322+
else if(is_string) jsonNode[nodename] = scalar;
323+
else jsonNode[nodename] = number;
249324
//YAML_LOG_d("[MAP][%d][%s] => %s(%s)", jsonNode.size()-1, nodename, is_string?"string":"number", is_string?scalar:String(number).c_str() );
250325
break;
251326
default: YAML_LOG_e("Error invalid nesting type"); break;
@@ -299,6 +374,7 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
299374
}
300375
}
301376

377+
302378
// JsonVariant deconstructor => YAML stream
303379
size_t serializeYml_JsonVariant( JsonVariant root, Stream &out, int depth, YAMLParser::JNestingType_t nt )
304380
{
@@ -346,20 +422,19 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
346422
return serializeYml_JsonVariant( src_obj, dest_stream, 0, YAMLParser::NONE );
347423
}
348424

349-
#if defined USE_STREAM_TO_STREAM
425+
#if defined USE_STREAM_TO_STREAM && !defined HAS_CJSON
350426
size_t serializeYml( Stream &json_src_stream, Stream &yaml_dest_stream )
351427
{
352-
JsonObject src_obj;
353-
if ( deserializeYml( src_obj, json_src_stream ) != DeserializationError::Ok ) {
354-
YAML_LOG_e("unable to deserialize to temporary JsonObject, aborting");
355-
return 0;
356-
}
357-
return serializeYml_JsonVariant( src_obj, yaml_dest_stream, 0, YAMLParser::NONE );
428+
YAMLToArduinoJson *parser = new YAMLToArduinoJson();
429+
JsonObject tmpObj = parser->toJson( json_src_stream ); // decode yaml stream/string
430+
auto ret = serializeYml_JsonVariant( tmpObj[ROOT_NODE], yaml_dest_stream, 0, YAMLParser::NONE );
431+
delete parser;
432+
return ret;
358433
}
359434
#endif
360435

361436

362-
DeserializationError deserializeYml( JsonDocument &dest_doc, Stream &src)
437+
DeserializationError deserializeYml( JsonDocument &dest_doc, Stream &src )
363438
{
364439
YAMLToArduinoJson *parser = new YAMLToArduinoJson();
365440
JsonObject tmpObj = parser->toJson( src ); // decode yaml stream/string
@@ -369,7 +444,7 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
369444
}
370445

371446

372-
DeserializationError deserializeYml( JsonDocument &dest_doc, const char *src)
447+
DeserializationError deserializeYml( JsonDocument &dest_doc, const char *src )
373448
{
374449
YAMLToArduinoJson *parser = new YAMLToArduinoJson();
375450
JsonObject tmpObj = parser->toJson( src ); // decode yaml stream/string
@@ -379,13 +454,13 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
379454
}
380455

381456

382-
#endif // __has_include(<ArduinoJson.h>)
457+
#endif // HAS_ARDUINOJSON
383458

384459

385460

386461

387462

388-
#if __has_include(<cJSON.h>)
463+
#if defined HAS_CJSON
389464

390465

391466
// yaml_node_t deconstructor => cJSON Object
@@ -406,7 +481,14 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
406481
char * end;
407482
scalar = (char *)yamlNode->data.scalar.value;
408483
number = strtod(scalar, &end);
409-
object = (end == scalar || *end) ? cJSON_CreateString(scalar) : cJSON_CreateNumber(number);
484+
if( (end == scalar || *end) ) { // string or bool
485+
bool bool_value;
486+
if( yaml_node_is_bool( yamlNode, &bool_value ) ) {
487+
object = cJSON_CreateBool( bool_value );
488+
} else {
489+
object = cJSON_CreateString( scalar );
490+
}
491+
} else object = cJSON_CreateNumber(number);
410492
}
411493
break;
412494
case YAML_SEQUENCE_NODE:
@@ -467,11 +549,18 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
467549
current_item = current_item->next;
468550
}
469551
} else {
552+
// TODO: figure out how to get cJSON value without quotes
470553
char *value = cJSON_PrintUnformatted( root );
471554
if( !value ) {
472555
YAML_LOG_e("node has no value!");
473556
return 0;
474557
}
558+
size_t value_len = strlen(value);
559+
if( ( value_len > 0 && value[0]=='"' && value[value_len-1]=='"' ) || ( value_len > 0 && value[0]=='\'' && value[value_len-1]=='\'' ) ) {
560+
// remove quotes from string
561+
memmove( value, value+1, value_len-1 );
562+
value[value_len-2] = '\0';
563+
}
475564
switch(nt) {
476565
case YAMLParser::SEQ_KEY:
477566
out_size += out.printf("\n%s%s%s", indent(depth), "- ", value );
@@ -503,4 +592,22 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e)
503592
}
504593

505594

506-
#endif // __has_include(<cJSON.h>)
595+
#if defined USE_STREAM_TO_STREAM
596+
size_t serializeYml( Stream &json_src_stream, Stream &yaml_dest_stream )
597+
{
598+
cJSON* objPtr = cJSON_Parse("{}"); // quick allocation of empty object
599+
if ( !deserializeYml( objPtr, json_src_stream ) ) {
600+
YAML_LOG_e("unable to deserialize to temporary cJSONObject, aborting");
601+
cJSON_Delete( objPtr );
602+
return 0;
603+
}
604+
size_t bytes_written = serializeYml_cJSONObject( objPtr, yaml_dest_stream, 0, YAMLParser::NONE );
605+
cJSON_Delete( objPtr );
606+
return bytes_written;
607+
}
608+
#endif
609+
610+
611+
612+
613+
#endif // HAS_CJSON

src/ArduinoYaml.hpp

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,38 @@ extern "C" {
3636
#include "libyaml/yaml.h" // https://github.com/yaml/libyaml
3737
}
3838

39+
40+
#if defined ESP32
41+
#define USE_STREAM_TO_STREAM
42+
#define HAS_CJSON
43+
#define HAS_ARDUINOJSON
44+
#endif
45+
46+
3947
#if defined ARDUINO_ARCH_SAMD || defined ARDUINO_ARCH_RP2040 || defined ESP8266
4048
// __has_include() macro only sees inside the sketch folder, so assume ArduinoJson as a default dependancy
4149
#include <Arduino.h>
4250
#include <assert.h>
4351
#include <ArduinoJson.h>
44-
// also disable the "unstable" stream-to-stream function
4552
#define HAS_ARDUINOJSON
53+
// also disable the "unstable" stream-to-stream function for low memory platforms
54+
#if !defined ARDUINO_ARCH_SAMD
55+
#define USE_STREAM_TO_STREAM
56+
#endif
4657
#endif
4758

4859
#if !defined HAS_ARDUINOJSON && __has_include(<ArduinoJson.h>)
4960
// esp32 __has_include() macro works outside the sketch folder, so it's possible to guess
5061
#define HAS_ARDUINOJSON
5162
#endif
5263

53-
#if defined ESP32
54-
// platform specific feature is unstable but recoverable with ESP32 devices family
55-
#define USE_STREAM_TO_STREAM
64+
#if !defined HAS_CJSON && __has_include(<cJSON.h>)
65+
// esp32 __has_include() macro works outside the sketch folder, so it's possible to guess
66+
#define HAS_CJSON
5667
#endif
5768

5869

70+
5971
// provide a default String::Stream reader/writer for internals
6072
class StringStream : public Stream
6173
{
@@ -85,12 +97,13 @@ class YAMLParser
8597
size_t bytesWritten() { return _bytes_written; }
8698
size_t bytesRead() { return _bytes_read; }
8799
String getYamlString() { return _yaml_string; }
100+
void setYamlStream( Stream* stream ) { _yaml_stream = stream; }
88101
enum JNestingType_t { NONE, SEQ_KEY, MAP_KEY };
89102
private:
90103
size_t _bytes_read;
91104
size_t _bytes_written;
92105
String _yaml_string;
93-
StringStream _yaml_stream = StringStream(_yaml_string);
106+
Stream *_yaml_stream = new StringStream(_yaml_string);
94107
void loadDocument();
95108
void handle_parser_error(yaml_parser_t *parser);
96109
void handle_emitter_error(yaml_emitter_t* emitter);
@@ -102,6 +115,11 @@ class YAMLParser
102115

103116

104117

118+
#if defined USE_STREAM_TO_STREAM
119+
// JSON stream to JsonObject to YAML stream
120+
size_t serializeYml( Stream &json_src_stream, Stream &yml_dest_stream );
121+
#endif
122+
105123

106124
#if defined HAS_ARDUINOJSON
107125

@@ -155,10 +173,6 @@ class YAMLParser
155173
size_t serializeYml( JsonVariant src_obj, String &dest_string );
156174
// ArduinoJSON object to YAML stream
157175
size_t serializeYml( JsonVariant src_obj, Stream &dest_stream );
158-
#if defined USE_STREAM_TO_STREAM
159-
// JSON stream to JsonObject to YAML stream
160-
size_t serializeYml( Stream &json_src_stream, Stream &yml_dest_stream );
161-
#endif
162176

163177
// Deserialize YAML string to ArduinoJSON document
164178
DeserializationError deserializeYml( JsonDocument &dest_doc, Stream &src);
@@ -174,6 +188,7 @@ class YAMLParser
174188
{
175189
YAMLToArduinoJson *parser = new YAMLToArduinoJson();
176190
dest_obj = parser->toJson( src ); // decode yaml stream/string
191+
dest_obj = dest_obj[ROOT_NODE];
177192
size_t capacity = parser->bytesWritten()*2;
178193
delete parser;
179194
if( capacity == 0 ) {
@@ -186,11 +201,11 @@ class YAMLParser
186201
}
187202

188203

189-
#endif
204+
#endif // HAS_ARDUINOJSON
190205

191206

192207

193-
#if __has_include(<cJSON.h>)
208+
#if defined HAS_CJSON
194209

195210
// cJSON friendly functions and derivated class
196211

@@ -207,7 +222,7 @@ class YAMLParser
207222
YAMLToCJson() {};
208223
~YAMLToCJson() { if(_root) cJSON_Delete(_root); };
209224
//void toJson();
210-
cJSON *toJson( yaml_document_t * document ){
225+
cJSON *toJson( yaml_document_t * document ) {
211226
yaml_node_t * node;
212227
if (node = yaml_document_get_root_node(document), !node) { YAML_LOG_w("No document defined."); return NULL; }
213228
return deserializeYml_cJSONObject(document, node);
@@ -240,20 +255,5 @@ class YAMLParser
240255
}
241256

242257

243-
#endif
244-
245-
#if defined ARDUINO_ARCH_SAMD
246-
// using slow copy instead of a macro, because std::string is incomplete with samd core
247-
static String _indent_str;
248-
static const char* indent( size_t size )
249-
{
250-
_indent_str = "";
251-
for( size_t i=0;i<size;i++ ) _indent_str += " ";
252-
return _indent_str.c_str();
253-
}
254-
#else
255-
// this macro does not like to be defined early (especially before ArduinoJson.h is included)
256-
#define indent(indent_size) (std::string(indent_size*2, ' ')).c_str()
257-
#endif
258-
258+
#endif // HAS_CJSON
259259

0 commit comments

Comments
 (0)