diff --git a/docs/mainpage.doxygen.tmpl b/docs/mainpage.doxygen.tmpl
index 1e11bc5..522f7bd 100644
--- a/docs/mainpage.doxygen.tmpl
+++ b/docs/mainpage.doxygen.tmpl
@@ -96,6 +96,8 @@ any data = parse_yaml(yaml_str);
- added the \c YamlRpcConnection class to the YamlRpcClient module
- updated the DataStreamClient module for complex types and new internal RestClient API changes (issue 2073)
- updated the DataStreamUtil module for complex types
+ - fixed a bug deserializing single-quoted strings; also serialized \c "!number" values will always include the tag to avoid
+ potential future ambiguity (issue 2343)
@subsection yaml051 yaml Module Version 0.5.1
diff --git a/src/QoreYamlEmitter.cpp b/src/QoreYamlEmitter.cpp
index 8e26cc7..821eba1 100644
--- a/src/QoreYamlEmitter.cpp
+++ b/src/QoreYamlEmitter.cpp
@@ -195,5 +195,6 @@ int QoreYamlEmitter::emitValue(const DateTime &d) {
else
d.format(str, "Z");
+ // issue #2343: to avoid ambiguity with single quoted strings, we always use the tag here
return emitScalar(str, YAML_TIMESTAMP_TAG);
}
diff --git a/src/QoreYamlParser.cpp b/src/QoreYamlParser.cpp
index 7401e89..dfd140f 100644
--- a/src/QoreYamlParser.cpp
+++ b/src/QoreYamlParser.cpp
@@ -213,7 +213,8 @@ static QoreNumberNode* parseNumber(const char* val, size_t len) {
}
// FIXME: this is still capable of false positives
-static AbstractQoreNode* try_parse_number(const char* val, size_t len) {
+static AbstractQoreNode* try_parse_number(const char* val, size_t len, bool no_simple_numeric = false) {
+ //printd(5, "try_parse_number() val: \"%s\" len: %d\n", val, (int)len);
bool sign = (*val == '-' || *val == '+');
// check for @inf@ and @nan@
@@ -235,7 +236,7 @@ static AbstractQoreNode* try_parse_number(const char* val, size_t len) {
return new QoreNumberNode(val, prec);
}
}
- return 0;
+ return nullptr;
}
const char* str = val + (int)sign;
@@ -266,21 +267,21 @@ static AbstractQoreNode* try_parse_number(const char* val, size_t len) {
od = false;
if (*str == '.') {
if (dp || e || pm)
- return 0;
+ return nullptr;
dp = true;
}
else if (*str == 'e' || *str == 'E') {
if (e || pm)
- return 0;
+ return nullptr;
e = true;
}
else if (*str == '+' || *str == '-') {
if (pm || !e)
- return 0;
+ return nullptr;
pm = true;
}
else
- return 0;
+ return nullptr;
++str;
}
@@ -293,6 +294,8 @@ static AbstractQoreNode* try_parse_number(const char* val, size_t len) {
|| (len == 20 && sign
&& ((*val == '+' && strcmp(val, "+9223372036854775807") <= 0)
|| (*val == '-' && strcmp(val, "-9223372036854775808") <= 0))))) {
+ if (no_simple_numeric)
+ return nullptr;
int64 iv = strtoll(val, 0, 10);
errno = 0;
assert(errno != ERANGE);
@@ -304,7 +307,7 @@ static AbstractQoreNode* try_parse_number(const char* val, size_t len) {
return new QoreNumberNode(val);
}
- return new QoreFloatNode(strtod(val, 0));
+ return no_simple_numeric ? nullptr : new QoreFloatNode(strtod(val, 0));
}
AbstractQoreNode* QoreYamlParser::parseScalar(bool favor_string) {
@@ -316,11 +319,23 @@ AbstractQoreNode* QoreYamlParser::parseScalar(bool favor_string) {
//printd(5, "QoreYamlParser::parseScalar() anchor=%s tag=%s value=%s len=%d plain_implicit=%d quoted_implicit=%d style=%d\n", event.data.scalar.anchor ? event.data.scalar.anchor : (yaml_char_t*)"n/a", event.data.scalar.tag ? event.data.scalar.tag : (yaml_char_t*)"n/a", val, len, event.data.scalar.plain_implicit, event.data.scalar.quoted_implicit, event.data.scalar.style);
if (!event.data.scalar.tag) {
- if (favor_string || (event.data.scalar.quoted_implicit && event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE)) {
+ if (favor_string || (event.data.scalar.quoted_implicit
+ && (event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE))) {
// assume it's a string
return new QoreStringNode(val, len, QCS_UTF8);
}
+ // issue #2343: could be a string, arbitrary-precision numeric or special floating-point value, or an ISO-8601 date/time value
+ if (event.data.scalar.quoted_implicit && event.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE) {
+ if (len > 9 && isdigit(val[0]) && isdigit(val[1]) && isdigit(val[2]) && isdigit(val[3]) && val[4] == '-'
+ && isdigit(val[5]) && isdigit(val[6]) && val[7] == '-'
+ && isdigit(val[8]) && isdigit(val[9]))
+ return parseAbsoluteDate();
+ // try
+ AbstractQoreNode* n = try_parse_number(val, len, true);
+ return n ? n : new QoreStringNode(val, len, QCS_UTF8);
+ }
+
// check for boolean values
if (!strcmp(val, "true"))
return &True;
@@ -329,14 +344,16 @@ AbstractQoreNode* QoreYamlParser::parseScalar(bool favor_string) {
// check for null
if (!strcmp(val, "null") || !strcmp(val, "~") || !len)
- return 0;
+ return nullptr;
// check for sqlnull
if (!strcmp(val, "sqlnull"))
return &Null;
// check for absolute date/time values
- if (isdigit(val[0]) && isdigit(val[1]) && isdigit(val[2]) && isdigit(val[3]) && val[4] == '-')
+ if (len > 9 && isdigit(val[0]) && isdigit(val[1]) && isdigit(val[2]) && isdigit(val[3]) && val[4] == '-'
+ && isdigit(val[5]) && isdigit(val[6]) && val[7] == '-'
+ && isdigit(val[8]) && isdigit(val[9]))
return parseAbsoluteDate();
// check for relative date/time values (durations)
@@ -363,6 +380,7 @@ AbstractQoreNode* QoreYamlParser::parseScalar(bool favor_string) {
}
const char* tag = (const char*)event.data.scalar.tag;
+ // FIXME: use a map here for O(ln(n)) performance
if (!strcmp(tag, YAML_TIMESTAMP_TAG))
return parseAbsoluteDate();
if (!strcmp(tag, YAML_BINARY_TAG))
@@ -370,7 +388,7 @@ AbstractQoreNode* QoreYamlParser::parseScalar(bool favor_string) {
if (!strcmp(tag, YAML_STR_TAG))
return new QoreStringNode(val, len, QCS_UTF8);
if (!strcmp(tag, YAML_NULL_TAG))
- return 0;
+ return nullptr;
if (!strcmp(tag, YAML_BOOL_TAG))
return parseBool();
if (!strcmp(tag, YAML_INT_TAG))
@@ -381,10 +399,12 @@ AbstractQoreNode* QoreYamlParser::parseScalar(bool favor_string) {
return new DateTimeNode(val);
if (!strcmp(tag, QORE_YAML_NUMBER_TAG))
return parseNumber(val, len);
+ if (!strcmp(tag, QORE_YAML_SQLNULL_TAG))
+ return &Null;
xsink->raiseException(QY_PARSE_ERR, "don't know how to parse scalar tag '%s'", tag);
- return 0;
+ return nullptr;
}
QoreBoolNode* QoreYamlParser::parseBool() {
diff --git a/src/yaml-module.h b/src/yaml-module.h
index 867826a..1ceb503 100644
--- a/src/yaml-module.h
+++ b/src/yaml-module.h
@@ -211,22 +211,22 @@ class QoreYamlEmitter : public QoreYamlBase {
return emit("map end");
}
- DLLLOCAL int emitScalar(const QoreString &value, const char *tag, const char *anchor = 0,
+ DLLLOCAL int emitScalar(const QoreString &value, const char *tag, const char *anchor = nullptr,
bool plain_implicit = true, bool quoted_implicit = true,
yaml_scalar_style_t style = YAML_ANY_SCALAR_STYLE) {
TempEncodingHelper str(&value, QCS_UTF8, xsink);
if (*xsink)
return -1;
- if (!yaml_scalar_event_initialize(&event, (yaml_char_t *)anchor, (yaml_char_t *)tag,
- (yaml_char_t *)str->getBuffer(), str->strlen(),
+ if (!yaml_scalar_event_initialize(&event, (yaml_char_t*)anchor, (yaml_char_t *)tag,
+ (yaml_char_t*)str->c_str(), str->strlen(),
plain_implicit, quoted_implicit, style))
return err("unknown error initializing yaml scalar output event for yaml type '%s', value '%s'", tag, value.getBuffer());
return emit("scalar", tag);
}
- DLLLOCAL int emitScalar(const char *value, const char *tag, const char *anchor = 0,
+ DLLLOCAL int emitScalar(const char *value, const char *tag, const char *anchor = nullptr,
bool plain_implicit = true, bool quoted_implicit = true,
yaml_scalar_style_t style = YAML_ANY_SCALAR_STYLE) {
if (!yaml_scalar_event_initialize(&event, (yaml_char_t *)anchor, (yaml_char_t *)tag,
@@ -283,7 +283,8 @@ class QoreYamlEmitter : public QoreYamlBase {
// append precision
tmp.sprintf("{%d}", n.getPrec());
//printd(5, "yaml emit number: %s\n", tmp.getBuffer());
- return emitScalar(tmp, QORE_YAML_NUMBER_TAG);
+ // issue #2343: to avoid ambiguity with single quoted strings, we always use the tag here
+ return emitScalar(tmp, QORE_YAML_NUMBER_TAG, nullptr, false, false);
}
DLLLOCAL int emitValue(bool b) {
diff --git a/test/yaml.qtest b/test/yaml.qtest
index 7e6f527..ad07b64 100755
--- a/test/yaml.qtest
+++ b/test/yaml.qtest
@@ -38,6 +38,8 @@ const DATA = (
-@inf@n,
@inf@,
-@inf@,
+ "'single quoted string'",
+ "'1234'",
);
const DATA2 = (
@@ -64,6 +66,7 @@ public class Main inherits QUnit::Test {
addTestCase("Len test", \testLen());
addTestCase("Date/Time test", \testDates());
addTestCase("Structure with NaN test", \testNanStructure());
+ addTestCase("single quoted strings", \testSingleQuotedStrings());
addTestCase("sql null test", \sqlNull());
# Return for compatibility with test harness that checks return value.
@@ -71,9 +74,7 @@ public class Main inherits QUnit::Test {
}
testYaml() {
- string s = make_yaml(DATA);
- any l = parse_yaml(s);
- testAssertion("test simple yaml", \equals(), (DATA, l));
+ assertEq(DATA, parse_yaml(make_yaml(DATA)));
}
testOptions() {
@@ -122,6 +123,13 @@ public class Main inherits QUnit::Test {
assertEq(DATA2[4], l[4]);
}
+ testSingleQuotedStrings() {
+ # issue 2343
+ assertEq("1234", parse_yaml("'1234'"));
+ assertEq(500n, parse_yaml("'5e+02n{128}'"));
+ assertEq(2017-10-26T09:37:36.119613+02:00, parse_yaml("'2017-10-26 09:37:36.119613 +02:00'"));
+ }
+
sqlNull() {
string yaml = make_yaml(NULL, EmitSqlNull);
assertRegex("^sqlnull", yaml);