Force null to be quoted if written as a string

This commit is contained in:
Jesse Beder 2015-01-24 16:07:10 -06:00
parent 087e0673f3
commit bc86fd4aec
2 changed files with 80 additions and 41 deletions

View File

@ -32,22 +32,29 @@ bool IsAnchorChar(int ch) { // test for ns-anchor-char
return true; return true;
} }
if (ch < 0x20) if (ch < 0x20) {
return false; return false;
}
if (ch < 0x7E) if (ch < 0x7E) {
return true; return true;
}
if (ch < 0xA0) if (ch < 0xA0) {
return false; return false;
if (ch >= 0xD800 && ch <= 0xDFFF) }
if (ch >= 0xD800 && ch <= 0xDFFF) {
return false; return false;
if ((ch & 0xFFFE) == 0xFFFE) }
if ((ch & 0xFFFE) == 0xFFFE) {
return false; return false;
if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) }
if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) {
return false; return false;
if (ch > 0x10FFFF) }
if (ch > 0x10FFFF) {
return false; return false;
}
return true; return true;
} }
@ -145,19 +152,27 @@ void WriteCodePoint(ostream_wrapper& out, int codePoint) {
bool IsValidPlainScalar(const std::string& str, FlowType::value flowType, bool IsValidPlainScalar(const std::string& str, FlowType::value flowType,
bool allowOnlyAscii) { bool allowOnlyAscii) {
if (str.empty()) if (str.empty()) {
return false; return false;
}
// first check the start // check against null
if (str == "null") {
return false;
}
// check the start
const RegEx& start = (flowType == FlowType::Flow ? Exp::PlainScalarInFlow() const RegEx& start = (flowType == FlowType::Flow ? Exp::PlainScalarInFlow()
: Exp::PlainScalar()); : Exp::PlainScalar());
if (!start.Matches(str)) if (!start.Matches(str)) {
return false; return false;
}
// and check the end for plain whitespace (which can't be faithfully kept in a // and check the end for plain whitespace (which can't be faithfully kept in a
// plain scalar) // plain scalar)
if (!str.empty() && *str.rbegin() == ' ') if (!str.empty() && *str.rbegin() == ' ') {
return false; return false;
}
// then check until something is disallowed // then check until something is disallowed
const RegEx& disallowed = (flowType == FlowType::Flow ? Exp::EndScalarInFlow() const RegEx& disallowed = (flowType == FlowType::Flow ? Exp::EndScalarInFlow()
@ -167,10 +182,12 @@ bool IsValidPlainScalar(const std::string& str, FlowType::value flowType,
Exp::Break() || Exp::Tab(); Exp::Break() || Exp::Tab();
StringCharSource buffer(str.c_str(), str.size()); StringCharSource buffer(str.c_str(), str.size());
while (buffer) { while (buffer) {
if (disallowed.Matches(buffer)) if (disallowed.Matches(buffer)) {
return false; return false;
if (allowOnlyAscii && (0x80 <= static_cast<unsigned char>(buffer[0]))) }
if (allowOnlyAscii && (0x80 <= static_cast<unsigned char>(buffer[0]))) {
return false; return false;
}
++buffer; ++buffer;
} }
@ -180,23 +197,27 @@ bool IsValidPlainScalar(const std::string& str, FlowType::value flowType,
bool IsValidSingleQuotedScalar(const std::string& str, bool escapeNonAscii) { bool IsValidSingleQuotedScalar(const std::string& str, bool escapeNonAscii) {
// TODO: check for non-printable characters? // TODO: check for non-printable characters?
for (std::size_t i = 0; i < str.size(); i++) { for (std::size_t i = 0; i < str.size(); i++) {
if (escapeNonAscii && (0x80 <= static_cast<unsigned char>(str[i]))) if (escapeNonAscii && (0x80 <= static_cast<unsigned char>(str[i]))) {
return false; return false;
if (str[i] == '\n') }
if (str[i] == '\n') {
return false; return false;
}
} }
return true; return true;
} }
bool IsValidLiteralScalar(const std::string& str, FlowType::value flowType, bool IsValidLiteralScalar(const std::string& str, FlowType::value flowType,
bool escapeNonAscii) { bool escapeNonAscii) {
if (flowType == FlowType::Flow) if (flowType == FlowType::Flow) {
return false; return false;
}
// TODO: check for non-printable characters? // TODO: check for non-printable characters?
for (std::size_t i = 0; i < str.size(); i++) { for (std::size_t i = 0; i < str.size(); i++) {
if (escapeNonAscii && (0x80 <= static_cast<unsigned char>(str[i]))) if (escapeNonAscii && (0x80 <= static_cast<unsigned char>(str[i]))) {
return false; return false;
}
} }
return true; return true;
} }
@ -226,8 +247,9 @@ bool WriteAliasName(ostream_wrapper& out, const std::string& str) {
int codePoint; int codePoint;
for (std::string::const_iterator i = str.begin(); for (std::string::const_iterator i = str.begin();
GetNextCodePointAndAdvance(codePoint, i, str.end());) { GetNextCodePointAndAdvance(codePoint, i, str.end());) {
if (!IsAnchorChar(codePoint)) if (!IsAnchorChar(codePoint)) {
return false; return false;
}
WriteCodePoint(out, codePoint); WriteCodePoint(out, codePoint);
} }
@ -241,18 +263,21 @@ StringFormat::value ComputeStringFormat(const std::string& str,
bool escapeNonAscii) { bool escapeNonAscii) {
switch (strFormat) { switch (strFormat) {
case Auto: case Auto:
if (IsValidPlainScalar(str, flowType, escapeNonAscii)) if (IsValidPlainScalar(str, flowType, escapeNonAscii)) {
return StringFormat::Plain; return StringFormat::Plain;
}
return StringFormat::DoubleQuoted; return StringFormat::DoubleQuoted;
case SingleQuoted: case SingleQuoted:
if (IsValidSingleQuotedScalar(str, escapeNonAscii)) if (IsValidSingleQuotedScalar(str, escapeNonAscii)) {
return StringFormat::SingleQuoted; return StringFormat::SingleQuoted;
}
return StringFormat::DoubleQuoted; return StringFormat::DoubleQuoted;
case DoubleQuoted: case DoubleQuoted:
return StringFormat::DoubleQuoted; return StringFormat::DoubleQuoted;
case Literal: case Literal:
if (IsValidLiteralScalar(str, flowType, escapeNonAscii)) if (IsValidLiteralScalar(str, flowType, escapeNonAscii)) {
return StringFormat::Literal; return StringFormat::Literal;
}
return StringFormat::DoubleQuoted; return StringFormat::DoubleQuoted;
default: default:
break; break;
@ -266,14 +291,16 @@ bool WriteSingleQuotedString(ostream_wrapper& out, const std::string& str) {
int codePoint; int codePoint;
for (std::string::const_iterator i = str.begin(); for (std::string::const_iterator i = str.begin();
GetNextCodePointAndAdvance(codePoint, i, str.end());) { GetNextCodePointAndAdvance(codePoint, i, str.end());) {
if (codePoint == '\n') if (codePoint == '\n') {
return false; // We can't handle a new line and the attendant indentation return false; // We can't handle a new line and the attendant indentation
// yet // yet
}
if (codePoint == '\'') if (codePoint == '\'') {
out << "''"; out << "''";
else } else {
WriteCodePoint(out, codePoint); WriteCodePoint(out, codePoint);
}
} }
out << "'"; out << "'";
return true; return true;
@ -307,15 +334,16 @@ bool WriteDoubleQuotedString(ostream_wrapper& out, const std::string& str,
default: default:
if (codePoint < 0x20 || if (codePoint < 0x20 ||
(codePoint >= 0x80 && (codePoint >= 0x80 &&
codePoint <= 0xA0)) // Control characters and non-breaking space codePoint <= 0xA0)) { // Control characters and non-breaking space
WriteDoubleQuoteEscapeSequence(out, codePoint); WriteDoubleQuoteEscapeSequence(out, codePoint);
else if (codePoint == 0xFEFF) // Byte order marks (ZWNS) should be } else if (codePoint == 0xFEFF) { // Byte order marks (ZWNS) should be
// escaped (YAML 1.2, sec. 5.2) // escaped (YAML 1.2, sec. 5.2)
WriteDoubleQuoteEscapeSequence(out, codePoint); WriteDoubleQuoteEscapeSequence(out, codePoint);
else if (escapeNonAscii && codePoint > 0x7E) } else if (escapeNonAscii && codePoint > 0x7E) {
WriteDoubleQuoteEscapeSequence(out, codePoint); WriteDoubleQuoteEscapeSequence(out, codePoint);
else } else {
WriteCodePoint(out, codePoint); WriteCodePoint(out, codePoint);
}
} }
} }
out << "\""; out << "\"";
@ -329,26 +357,27 @@ bool WriteLiteralString(ostream_wrapper& out, const std::string& str,
int codePoint; int codePoint;
for (std::string::const_iterator i = str.begin(); for (std::string::const_iterator i = str.begin();
GetNextCodePointAndAdvance(codePoint, i, str.end());) { GetNextCodePointAndAdvance(codePoint, i, str.end());) {
if (codePoint == '\n') if (codePoint == '\n') {
out << "\n" << IndentTo(indent); out << "\n" << IndentTo(indent);
else } else {
WriteCodePoint(out, codePoint); WriteCodePoint(out, codePoint);
}
} }
return true; return true;
} }
bool WriteChar(ostream_wrapper& out, char ch) { bool WriteChar(ostream_wrapper& out, char ch) {
if (('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')) if (('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')) {
out << ch; out << ch;
else if ((0x20 <= ch && ch <= 0x7e) || ch == ' ') } else if ((0x20 <= ch && ch <= 0x7e) || ch == ' ') {
out << "\"" << ch << "\""; out << "\"" << ch << "\"";
else if (ch == '\t') } else if (ch == '\t') {
out << "\"\\t\""; out << "\"\\t\"";
else if (ch == '\n') } else if (ch == '\n') {
out << "\"\\n\""; out << "\"\\n\"";
else if (ch == '\b') } else if (ch == '\b') {
out << "\"\\b\""; out << "\"\\b\"";
else { } else {
out << "\""; out << "\"";
WriteDoubleQuoteEscapeSequence(out, ch); WriteDoubleQuoteEscapeSequence(out, ch);
out << "\""; out << "\"";
@ -391,16 +420,18 @@ bool WriteTag(ostream_wrapper& out, const std::string& str, bool verbatim) {
const RegEx& reValid = verbatim ? Exp::URI() : Exp::Tag(); const RegEx& reValid = verbatim ? Exp::URI() : Exp::Tag();
while (buffer) { while (buffer) {
int n = reValid.Match(buffer); int n = reValid.Match(buffer);
if (n <= 0) if (n <= 0) {
return false; return false;
}
while (--n >= 0) { while (--n >= 0) {
out << buffer[0]; out << buffer[0];
++buffer; ++buffer;
} }
} }
if (verbatim) if (verbatim) {
out << ">"; out << ">";
}
return true; return true;
} }
@ -410,8 +441,9 @@ bool WriteTagWithPrefix(ostream_wrapper& out, const std::string& prefix,
StringCharSource prefixBuffer(prefix.c_str(), prefix.size()); StringCharSource prefixBuffer(prefix.c_str(), prefix.size());
while (prefixBuffer) { while (prefixBuffer) {
int n = Exp::URI().Match(prefixBuffer); int n = Exp::URI().Match(prefixBuffer);
if (n <= 0) if (n <= 0) {
return false; return false;
}
while (--n >= 0) { while (--n >= 0) {
out << prefixBuffer[0]; out << prefixBuffer[0];
@ -423,8 +455,9 @@ bool WriteTagWithPrefix(ostream_wrapper& out, const std::string& prefix,
StringCharSource tagBuffer(tag.c_str(), tag.size()); StringCharSource tagBuffer(tag.c_str(), tag.size());
while (tagBuffer) { while (tagBuffer) {
int n = Exp::Tag().Match(tagBuffer); int n = Exp::Tag().Match(tagBuffer);
if (n <= 0) if (n <= 0) {
return false; return false;
}
while (--n >= 0) { while (--n >= 0) {
out << tagBuffer[0]; out << tagBuffer[0];

View File

@ -956,6 +956,12 @@ TEST_F(EmitterTest, ForceSingleQuotedToDouble) {
ExpectEmit("\"Hello\\nWorld\""); ExpectEmit("\"Hello\\nWorld\"");
} }
TEST_F(EmitterTest, QuoteNull) {
out << "null";
ExpectEmit("\"null\"");
}
class EmitterErrorTest : public ::testing::Test { class EmitterErrorTest : public ::testing::Test {
protected: protected:
void ExpectEmitError(const std::string& expectedError) { void ExpectEmitError(const std::string& expectedError) {