From af6151f2f34fb44ef4631d9c4de874b7dd13d7c6 Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Fri, 17 Mar 2023 17:37:28 +0100 Subject: yaml: missing values are actually null/~, not empty strings Change-Id: I6ca98e219bf4c3de9a1eaaae5ddeb7e7cfa27107 Reviewed-by: Dominik Holland (cherry picked from commit 0ae6d0a3637075fe8005361177eae3142eee0a77) Reviewed-by: Qt Cherry-pick Bot --- src/common-lib/qtyaml.cpp | 144 +++++++++++++++++++++-------------------- tests/auto/yaml/data/test.yaml | 1 + tests/auto/yaml/tst_yaml.cpp | 2 + 3 files changed, 77 insertions(+), 70 deletions(-) diff --git a/src/common-lib/qtyaml.cpp b/src/common-lib/qtyaml.cpp index e0ff3d1b..2d64a3c9 100644 --- a/src/common-lib/qtyaml.cpp +++ b/src/common-lib/qtyaml.cpp @@ -290,9 +290,8 @@ QVariant YamlParser::parseScalar() const { QString scalar = parseString(); - if (scalar.isEmpty() - || d->event.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE - || d->event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE) { + if (d->event.data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE + || d->event.data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE) { return scalar; } @@ -537,79 +536,84 @@ QStringList YamlParser::parseStringOrStringList() } } -void YamlParser::parseFields(const std::vector &fields) +static QString mapEventNames(const QVector &events) { - if (!isMap()) - throw YamlParserException(this, "Expected a map (type %1) to parse fields from, but got type %2") - .arg(YAML_MAPPING_START_EVENT).arg(d->event.type); + static const QHash eventNames = { + { YAML_NO_EVENT, "nothing" }, + { YAML_STREAM_START_EVENT, "stream start" }, + { YAML_STREAM_END_EVENT, "stream end" }, + { YAML_DOCUMENT_START_EVENT, "document start" }, + { YAML_DOCUMENT_END_EVENT, "document end" }, + { YAML_ALIAS_EVENT, "alias" }, + { YAML_SCALAR_EVENT, "scalar" }, + { YAML_SEQUENCE_START_EVENT, "sequence start" }, + { YAML_SEQUENCE_END_EVENT, "sequence end" }, + { YAML_MAPPING_START_EVENT, "mapping start" }, + { YAML_MAPPING_END_EVENT, "mapping end" } + }; + QString names; + for (int i = 0; i < events.size(); ++i) { + if (i) + names.append(i == (events.size() - 1) ? qL1S(" or ") : qL1S(", ")); + names.append(qL1S(eventNames.value(events.at(i), ""))); + } + return names; +}; +void YamlParser::parseFields(const std::vector &fields) +{ QVector fieldsFound; - while (true) { - nextEvent(); // read key - if (d->event.type == YAML_MAPPING_END_EVENT) - break; - QString key = parseMapKey(); - if (fieldsFound.contains(key)) - throw YamlParserException(this, "Found duplicate key '%1' in mapping").arg(key); - - auto field = fields.cbegin(); - for (; field != fields.cend(); ++field) { - if (key == qL1S(field->name)) - break; - } - if (field == fields.cend()) - throw YamlParserException(this, "Field '%1' is not valid in this context").arg(key); - fieldsFound << key; - - nextEvent(); // read value - QVector allowedEvents; - if (field->types & YamlParser::Scalar) - allowedEvents.append(YAML_SCALAR_EVENT); - if (field->types & YamlParser::Map) - allowedEvents.append(YAML_MAPPING_START_EVENT); - if (field->types & YamlParser::List) - allowedEvents.append(YAML_SEQUENCE_START_EVENT); - - if (!allowedEvents.contains(d->event.type)) { // ALIASES MISSING HERE! - auto mapEventNames = [](const QVector &events) -> QString { - static const QHash eventNames = { - { YAML_NO_EVENT, "nothing" }, - { YAML_STREAM_START_EVENT, "stream start" }, - { YAML_STREAM_END_EVENT, "stream end" }, - { YAML_DOCUMENT_START_EVENT, "document start" }, - { YAML_DOCUMENT_END_EVENT, "document end" }, - { YAML_ALIAS_EVENT, "alias" }, - { YAML_SCALAR_EVENT, "scalar" }, - { YAML_SEQUENCE_START_EVENT, "sequence start" }, - { YAML_SEQUENCE_END_EVENT, "sequence end" }, - { YAML_MAPPING_START_EVENT, "mapping start" }, - { YAML_MAPPING_END_EVENT, "mapping end" } - }; - QString names; - for (int i = 0; i < events.size(); ++i) { - if (i) - names.append(i == (events.size() - 1) ? qL1S(" or ") : qL1S(", ")); - names.append(qL1S(eventNames.value(events.at(i), ""))); - } - return names; - }; - - throw YamlParserException(this, "Field '%1' expected to be of type '%2', but got '%3'") - .arg(field->name).arg(mapEventNames(allowedEvents)).arg(mapEventNames({ d->event.type })); + if (!isMap()) { + // an empty document is ok - we just have to check for required fields below + if (!isScalar() || (parseScalar() != QVariant::fromValue(nullptr))) { + throw YamlParserException(this, "Expected a map (type '%1') to parse fields from, but got type '%2'") + .arg(mapEventNames({ YAML_MAPPING_START_EVENT })).arg(mapEventNames({ d->event.type })); } + } else { + while (true) { + nextEvent(); // read key + if (d->event.type == YAML_MAPPING_END_EVENT) + break; + QString key = parseMapKey(); + if (fieldsFound.contains(key)) + throw YamlParserException(this, "Found duplicate key '%1' in mapping").arg(key); + + auto field = fields.cbegin(); + for (; field != fields.cend(); ++field) { + if (key == qL1S(field->name)) + break; + } + if (field == fields.cend()) + throw YamlParserException(this, "Field '%1' is not valid in this context").arg(key); + fieldsFound << key; + + nextEvent(); // read value + QVector allowedEvents; + if (field->types & YamlParser::Scalar) + allowedEvents.append(YAML_SCALAR_EVENT); + if (field->types & YamlParser::Map) + allowedEvents.append(YAML_MAPPING_START_EVENT); + if (field->types & YamlParser::List) + allowedEvents.append(YAML_SEQUENCE_START_EVENT); + + if (!allowedEvents.contains(d->event.type)) { // ALIASES MISSING HERE! + throw YamlParserException(this, "Field '%1' expected to be of type '%2', but got '%3'") + .arg(field->name).arg(mapEventNames(allowedEvents)).arg(mapEventNames({ d->event.type })); + } - yaml_event_type_t typeBefore = d->event.type; - yaml_event_type_t typeAfter; - switch (typeBefore) { - case YAML_MAPPING_START_EVENT: typeAfter = YAML_MAPPING_END_EVENT; break; - case YAML_SEQUENCE_START_EVENT: typeAfter = YAML_SEQUENCE_END_EVENT; break; - default: typeAfter = typeBefore; break; - } - field->callback(this); - if (d->event.type != typeAfter) { - throw YamlParserException(this, "Invalid YAML event state after field callback for '%3': expected %1, but got %2") - .arg(typeAfter).arg(d->event.type).arg(key); + yaml_event_type_t typeBefore = d->event.type; + yaml_event_type_t typeAfter; + switch (typeBefore) { + case YAML_MAPPING_START_EVENT: typeAfter = YAML_MAPPING_END_EVENT; break; + case YAML_SEQUENCE_START_EVENT: typeAfter = YAML_SEQUENCE_END_EVENT; break; + default: typeAfter = typeBefore; break; + } + field->callback(this); + if (d->event.type != typeAfter) { + throw YamlParserException(this, "Invalid YAML event state after field callback for '%3': expected %1, but got %2") + .arg(typeAfter).arg(d->event.type).arg(key); + } } } QStringList fieldsMissing; diff --git a/tests/auto/yaml/data/test.yaml b/tests/auto/yaml/data/test.yaml index b16e3d39..7b2e5e84 100644 --- a/tests/auto/yaml/data/test.yaml +++ b/tests/auto/yaml/data/test.yaml @@ -15,6 +15,7 @@ bool-false: false bool-no: no null-literal: null null-tilde: ~ +null-empty: string-unquoted: unquoted string-singlequoted: 'singlequoted' string-doublequoted: "doublequoted" diff --git a/tests/auto/yaml/tst_yaml.cpp b/tests/auto/yaml/tst_yaml.cpp index 344f0d15..c9f3394c 100644 --- a/tests/auto/yaml/tst_yaml.cpp +++ b/tests/auto/yaml/tst_yaml.cpp @@ -52,6 +52,7 @@ void tst_Yaml::parser() { "bool-no", false }, { "null-literal", vnull }, { "null-tilde", vnull }, + { "null-empty", vnull }, { "string-unquoted", QVariant::fromValue(qSL("unquoted")) }, { "string-singlequoted", QVariant::fromValue(qSL("singlequoted")) }, { "string-doublequoted", QVariant::fromValue(qSL("doublequoted")) }, @@ -176,6 +177,7 @@ static const QVariantMap testMainDoc = { { qSL("bool-no"), false }, { qSL("null-literal"), vnull }, { qSL("null-tilde"), vnull }, + { qSL("null-empty"), vnull }, { qSL("string-unquoted"), qSL("unquoted") }, { qSL("string-singlequoted"), qSL("singlequoted") }, { qSL("string-doublequoted"), qSL("doublequoted") }, -- cgit v1.2.1