diff --git a/docs/src/config/ini-config.adoc b/docs/src/config/ini-config.adoc index 6ffc2b06906..69fda476554 100644 --- a/docs/src/config/ini-config.adoc +++ b/docs/src/config/ini-config.adoc @@ -45,17 +45,17 @@ DISPLAY = axis In this list, the DISPLAY variable will be set to axis because the other one is commented out. If someone carelessly edits a list like this and leaves two of the lines uncommented, the first one encountered will be used. -Note that inside a variable's value, the "#" and ";" characters are still comments. -You should use double or single quoted strings if you need to embed # or ; characters: +Note that inside a variable's value, the "#" and ";" characters are part of the value: [source,{ini}] ---- -# Below results in INCORRECT=value -# because comments are stripped -INCORRECT = value # and a comment +# Below does not result in INCORRECT=value +# because comments are not interpreted as comments in values +INCORRECT = value # and this is not a comment -# Correct embedding -CORRECT = "value # and an embedded # char" +# Correct comment +# hash char # is a comment om this line +CORRECT = value ---- [[sub:ini:sections]] @@ -119,11 +119,9 @@ ini.2.max_velocity A specific variable in a specific sections is often denoted in the documentation as [SECTION]VARIABLE. This specification mirrors the same way they are specified in HAL files for expansion. -Variable values may be quoted to ensure proper space and special character embedding in literal or escaped forms. -Both single and double quoted values are allowed. -Mutiple quoted value segments are merged into one value. -Embedding quotes in quoted values must use the backslash \\ character to escape the embedded quote if it is the same kind as the enclosing quotes. -Double quotes values also support all common escape formats and full Unicode: +Variable values may embed special characters in literal or escaped forms. +Single or double quotes are not treated as special and are a literal part of the value. +Values also support all common escape formats and full Unicode: * control: \\[abfnrtv] * octal: \\[0-2][0-7]{0,2} @@ -134,27 +132,26 @@ Double quotes values also support all common escape formats and full Unicode: The resulting value is always converted into UTF-8 and checked for validity. It is not allowed to embed NUL characters either literally or by using an escape. -.Value Quote and Escape Example +Leading and trailing white space is normally removed from a value. +You can add leading and trailing space in a value by using an escaped value for space (\\x20) as first or last character. +Spaces inside a value are automatically part of the value. +Tabs and newlines can be added using \\t and \\n. + +.Value Escape Example [source,{ini}] ---- -STRING = "Hello World!" -STRING = 'Hello World Too!' +STRING = Hello World! # The following would become: Hello World! -STRING = "Hello" \ -" " \ -'World' \ -"!" - -EMBED = "Literal single ' in double" -EMBED = 'Literal double " in single' -EMBED = "Literal double \" in double" -EMBED = 'Literal single \' in single' - -SMILE = "\\370\\237\\230\\200 = 😀" -SMILE = "\\xf0\\x9f\\x98\\x80 = 😀" -SMILE = "\\ud83d\\ude00 = 😀" -SMILE = "\\U0001f600 = 😀" +STRING = Hello\ + \ +World\ +! + +SMILE = \\370\\237\\230\\200 = 😀 +SMILE = \\xf0\\x9f\\x98\\x80 = 😀 +SMILE = \\ud83d\\ude00 = 😀 +SMILE = \\U0001f600 = 😀 ---- Variables' value can have types associated when they are read by LinuxCNC. diff --git a/src/emc/ini/inifile.cc b/src/emc/ini/inifile.cc index c31e50da25a..5616a2ff317 100644 --- a/src/emc/ini/inifile.cc +++ b/src/emc/ini/inifile.cc @@ -280,14 +280,12 @@ static bool isValidUTF8(const std::string &s) // // Process a value to find strings // Modifies the 'value' argument to its final version -// A string value is extracted if the value starts with a single "'" or double -// '"' quote. Otherwise, the value is stripped of optional comments and -// returned. A string may be broken into parts like: -// "foo" "bar" 'zap' "bling" -// and such string will result in returning "foobarzapbling". -// Escape substitution is performed only for double quote enclosed strings. -// Embedding characters with \ooo or \xHH escapes may result in invalid UTF-8 -// strings but that is checked later. Possible escapes: +// Embedded quotes are no special characters and are copied verbatim to the +// output. Comments character # and ; are not special in the value and also +// copied verbatim. +// Escape substitution is performed on the entire value. Embedding characters +// with \ooo or \xHH escapes may result in invalid UTF-8 strings but that is +// checked later. Possible escapes: // - \[abfnrtv] Standard C-type escapes // - \[0-3][0-7]{0,2} Standard C-type octal escape // - \x[a-fA-F0-9]{2} Hex 8-bit character escape @@ -304,262 +302,176 @@ bool IniFileContent::processValue(std::string &value, const std::string &path, u if(value.empty()) return true; - if(std::string::npos != value.find((char)0)) { - print_msg(fmt::format("{}:{}: error: Embedded literal NUL character not supported", path, linenr)); - return false; - } - // We should be l/r trimmed getting here. - if('"' != value[0] && '\'' != value[0]) { - // No string markers at start, still need to strip comment - size_t c = value.find_first_of("#;"); - if(std::string::npos != c) { - // Found comment marker - value.erase(c); // Remove comment - IniFile::rtrim(value); // Remove trailing whitespace - } - if(std::string::npos != value.find((char)0)) { - // Trying to embed a NUL char - print_msg(fmt::format("{}:{}: error: NUL character not supported in values", path, linenr)); + std::string realstr; + for(size_t pos = 0; pos < value.size(); pos++) { + uint32_t hexval; + + char ch = value[pos]; + if(0 == ch) { + // Trying to add a NUL char + print_msg(fmt::format("{}:{}: error: Embedded literal NUL character not supported", path, linenr)); return false; } - return true; - } - // Detected the string marker 'quote'. Need to start collecting enclosed - // strings and parse embedded escapes. - std::string realstr; - uint32_t hexval; - size_t pos; - char quote = 0; - enum { ST_START, ST_COLLECT } state = ST_START; - for(pos = 0; pos < value.size(); pos++) { - char ch = value[pos]; - switch(state) { - case ST_START: - if('"' == ch || '\'' == ch) { - quote = ch; - state = ST_COLLECT; - } else if (!IniFile::isSpace(ch)) { - // Not a space - if('#' == ch || ';' == ch) { - pos = value.size(); // Comment until end-of-line - } else { - // Some junk between strings - // Can only do all enclosed strings or none enclosed - print_msg(fmt::format("{}:{}: error: Expected single or double quote, got '{}' at position {} of the value", - path, linenr, ch, pos+1)); - return false; - } - } // else it is a space and we just ignore it - break; - case ST_COLLECT: - if(ch == quote) { - // End of this string (fragment) - state = ST_START; - break; - } - if(0 == ch) { - // Trying to add a NUL char - print_msg(fmt::format("{}:{}: error: NUL character not supported in quoted values", path, linenr)); + // Check for an escape + if('\\' == ch) { + // An escape, see what follows + size_t cleft = value.size() - pos - 1; // Nr of characters after the '\' + if(cleft < 1) { + // Last char is an escape + // This should never happen because it would have been seen + // as a continuation. The error happens when it fails to + // detect the end quote. + print_msg(fmt::format("{}:{}: internal error: End of line while parsing '\\' escape", path, linenr)); return false; } - - // For single quote strings we (almost) unconditionally append - if('\'' == quote) { - if('\\' == ch) { - // There must always be a following character when we see - // an escape, regardless what it contains (char at pos is - // 1, next is 2). - if(value.size() - pos < 2) { - print_msg(fmt::format("{}:{}: error: End of line while parsing '\\' escape", path, linenr)); - return false; - } else if('\'' == value[pos+1]) { - // Escapes the single quote - pos++; // Eat the '\\' and add the quote - realstr += '\''; - } else { - realstr += ch; // Just a literal '\\' + auto nch = value[pos+1]; // The letter after the escape + switch(nch) { + case 'a': realstr += '\a'; break; + case 'b': realstr += '\b'; break; + case 'f': realstr += '\f'; break; + case 'n': realstr += '\n'; break; + case 'r': realstr += '\r'; break; + case 't': realstr += '\t'; break; + case 'v': realstr += '\v'; break; + case '\\': realstr += '\\'; break; + case '0': // Octal value \0 ... \377 + case '1': + case '2': + case '3': + // Have at least \o, can have \oo or \ooo + hexval = nch - '0'; + if(cleft >= 2 && value[pos+2] >= '0' && value[pos+2] <= '7') { + // Have at least \oo, can have \ooo + hexval <<= 3; + hexval += value[pos+2] - '0'; + if(cleft >= 3 && value[pos+3] >= '0' && value[pos+3] <= '7') { + // Have \ooo + hexval <<= 3; + hexval += value[pos+3] - '0'; + pos++; // Eat the third digit } - } else { - realstr += ch; // Just a character + pos++; // Eat the second digit } - break; // We're done when single quoted - } - - // Now we must have a double quoted string, test escapes - if('\\' == ch) { - // An escape, see what follows - size_t cleft = value.size() - pos - 1; // Nr of characters after the '\' - if(cleft < 1) { - // Last char is an escape - // This should never happen because it would have been seen - // as a continuation. The error happens when it fails to - // detect the end quote. - print_msg(fmt::format("{}:{}: internal error: End of line while parsing '\\' escape", path, linenr)); + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded octal NUL character not supported", path, linenr)); return false; } - auto nch = value[pos+1]; // The letter after the escape - switch(nch) { - case '"': - case '\'': - if(quote != nch) { - // Wrongly escaped quote - print_msg(fmt::format("{}:{}: warning: Improper escape of {} quote, ignored", path, linenr, nch)); - } - realstr += nch; - break; - case 'a': realstr += '\a'; break; - case 'b': realstr += '\b'; break; - case 'f': realstr += '\f'; break; - case 'n': realstr += '\n'; break; - case 'r': realstr += '\r'; break; - case 't': realstr += '\t'; break; - case 'v': realstr += '\v'; break; - case '0': // Octal value \0 ... \377 - case '1': - case '2': - case '3': - // Have at least \o, can have \oo or \ooo - hexval = nch - '0'; - if(cleft >= 2 && value[pos+2] >= '0' && value[pos+2] <= '7') { - // Have at least \oo, can have \ooo - hexval <<= 3; - hexval += value[pos+2] - '0'; - if(cleft >= 3 && value[pos+3] >= '0' && value[pos+3] <= '7') { - // Have \ooo - hexval <<= 3; - hexval += value[pos+3] - '0'; - pos++; // Eat the third digit - } - pos++; // Eat the second digit - } - if(!hexval) { - // Trying to embed a NUL char - print_msg(fmt::format("{}:{}: error: Embedded octal NUL character not supported", path, linenr)); - return false; - } - // Note that this can result in invalid UTF-8, but we don't care here - realstr += (char)hexval; - break; - case 'x': // Hex value \xHH - if(cleft < 3+1) { - // Need at least 3 chars for xHH and one for the closing quote - print_msg(fmt::format("{}:{}: error: Improper hex escape", path, linenr)); - return false; - } - if(!fromHex(value.substr(pos+2, 2), hexval)) { - // Invalid hex number - print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in hex escape", - path, linenr, value.substr(pos+2, 2))); - return false; - } - if(!hexval) { - // Trying to embed a NUL char - print_msg(fmt::format("{}:{}: error: Embedded hex NUL character not supported", path, linenr)); - return false; - } - // Note that this can result in invalid UTF-8, but we don't care here - realstr += (char)hexval; - pos += 2; // Eat the xHH characters - break; - case 'u': // 16-bit hex \uXXXX (actually, this is an UTF-16 abomination) - if(cleft < 5+1) { - // Need at least 5 chars for uXXXX and one for the closing quote - print_msg(fmt::format("{}:{}: error: Improper UTF-16 escape", path, linenr)); - return false; - } - if(!fromHex(value.substr(pos+2, 4), hexval)) { - // Invalid hex number - print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-16 escape", - path, linenr, value.substr(pos+2, 4))); - return false; - } - if(!hexval) { - // Trying to embed a NUL char - print_msg(fmt::format("{}:{}: error: Embedded UTF-16 NUL character not supported", path, linenr)); + // Note that this can result in invalid UTF-8, but we don't care here + realstr += (char)hexval; + break; + case 'x': // Hex value \xHH + if(cleft < 3) { + // Need at least 3 chars for xHH + print_msg(fmt::format("{}:{}: error: Improper hex escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 2), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in hex escape", + path, linenr, value.substr(pos+2, 2))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded hex NUL character not supported", path, linenr)); + return false; + } + // Note that this can result in invalid UTF-8, but we don't care here + realstr += (char)hexval; + pos += 2; // Eat the xHH characters + break; + case 'u': // 16-bit hex \uXXXX (actually, this is an UTF-16 abomination) + if(cleft < 5) { + // Need at least 5 chars for uXXXX + print_msg(fmt::format("{}:{}: error: Improper UTF-16 escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 4), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-16 escape", + path, linenr, value.substr(pos+2, 4))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded UTF-16 NUL character not supported", path, linenr)); + return false; + } + if(hexval >= 0xd800 && hexval <= 0xdfff) { + // We're in UTF-16 surrogate wonderland + if(hexval >= 0xdc00) { + // The high surrogate must be 0xd800..0xdbff + print_msg(fmt::format("{}:{}: error: Invalid high surrogate value u{:04X} in UTF-16", + path, linenr, hexval)); return false; } - if(hexval >= 0xd800 && hexval <= 0xdfff) { - // We're in UTF-16 surrogate wonderland - if(hexval >= 0xdc00) { - // The high surrogate must be 0xd800..0xdbff - print_msg(fmt::format("{}:{}: error: Invalid high surrogate value u{:04X} in UTF-16", - path, linenr, hexval)); - return false; - } - if(cleft < 5+1 + 6 || '\\' != value[pos+6] || 'u' != value[pos+7]) { - // We need to have room for the second surrogate: uXXXX\uYYYY (plus the closing quote) - print_msg(fmt::format("{}:{}: error: Missing low surrogate in UTF-16", - path, linenr, pos)); - return false; - } - uint32_t hexsur; - if(!fromHex(value.substr(pos+8, 4), hexsur)) { - // Invalid hex number - print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in UTF-16 low surrogate escape", - path, linenr, value.substr(pos+8, 4))); - return false; - } - if(hexsur < 0xdc00 || hexsur > 0xdfff) { - // The low surrogate must be 0xdc00..0xdfff - print_msg(fmt::format("{}:{}: error: Invalid low surrogate value u{:04X} in UTF-16", - path, linenr, hexsur)); - return false; - } - hexval = 0x10000 + ((hexval & 0x03ff) << 10) + (hexsur & 0x3ff); - pos += 6; // Eat the entire low surrogate \uYYYY - } - realstr += toUTF8(hexval); - pos += 4; // Eat the XXXX characters - break; - case 'U': // 32-bit hex \UXXXXXXXX (UTF-32) - if(cleft < 9+1) { - // Need at least 9 chars for UXXXXXXXX plus one for the closing quote - print_msg(fmt::format("{}:{}: error: Improper UTF-32 escape", path, linenr)); + if(cleft < 5 + 6 || '\\' != value[pos+6] || 'u' != value[pos+7]) { + // We need to have room for the second surrogate: uXXXX\uYYYY + print_msg(fmt::format("{}:{}: error: Missing low surrogate in UTF-16", + path, linenr, pos)); return false; } - if(!fromHex(value.substr(pos+2, 8), hexval)) { + uint32_t hexsur; + if(!fromHex(value.substr(pos+8, 4), hexsur)) { // Invalid hex number - print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-32 escape", - path, linenr, value.substr(pos+2, 8))); - return false; - } - if(!hexval) { - // Trying to embed a NUL char - print_msg(fmt::format("{}:{}: error: Embedded UTF-32 NUL character not supported", path, linenr)); + print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in UTF-16 low surrogate escape", + path, linenr, value.substr(pos+8, 4))); return false; } - if(hexval > 0x10ffff) { - // Invalid code point - print_msg(fmt::format("{}:{}: error: Invalid code point U{:08X} in UTF-32 escape", - path, linenr, hexval)); + if(hexsur < 0xdc00 || hexsur > 0xdfff) { + // The low surrogate must be 0xdc00..0xdfff + print_msg(fmt::format("{}:{}: error: Invalid low surrogate value u{:04X} in UTF-16", + path, linenr, hexsur)); return false; } - realstr += toUTF8(hexval); - pos += 8; // Eat the XXXXXXXX characters - break; - default: - print_msg(fmt::format("{}:{}: warning: Improper escape of '{}', ignored", path, linenr, nch)); - realstr += nch; - break; + hexval = 0x10000 + ((hexval & 0x03ff) << 10) + (hexsur & 0x3ff); + pos += 6; // Eat the entire low surrogate \uYYYY } - pos++; // Eat the escape char - } else { - // Not escaped, just copy - realstr += ch; + realstr += toUTF8(hexval); + pos += 4; // Eat the XXXX characters + break; + case 'U': // 32-bit hex \UXXXXXXXX (UTF-32) + if(cleft < 9) { + // Need at least 9 chars for UXXXXXXXX + print_msg(fmt::format("{}:{}: error: Improper UTF-32 escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 8), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-32 escape", + path, linenr, value.substr(pos+2, 8))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded UTF-32 NUL character not supported", path, linenr)); + return false; + } + if(hexval > 0x10ffff) { + // Invalid code point + print_msg(fmt::format("{}:{}: error: Invalid code point U{:08X} in UTF-32 escape", + path, linenr, hexval)); + return false; + } + realstr += toUTF8(hexval); + pos += 8; // Eat the XXXXXXXX characters + break; + default: + print_msg(fmt::format("{}:{}: warning: Improper escape of '{}', ignored", path, linenr, nch)); + realstr += '\\'; // Just add the literal backslash + realstr += nch; + break; } - break; - default: - print_msg(fmt::format("{}:{}: internal error: Invalid state '{}' in string collector", path, linenr, (int)state)); - return false; + pos++; // Eat the escape char + } else { + // Not escaped, just copy + realstr += ch; } } - if(ST_START != state) { - print_msg(fmt::format("{}:{}: error: Missing terminating {} quote", path, linenr, quote)); - return false; - } value = realstr; return true; diff --git a/src/emc/ini/inivalue.cc b/src/emc/ini/inivalue.cc index 4bd4b9ac1d9..f854db3f6bb 100644 --- a/src/emc/ini/inivalue.cc +++ b/src/emc/ini/inivalue.cc @@ -160,6 +160,42 @@ static std::optional convertReal(const std::string &optval) return r; } +// Escape all special characters in a double quoted string +static const std::string escapeString(const std::string &s) +{ + std::string escv{"\a\b\f\n\r\t\v"}; + std::string escc{"abfnrtv"}; // Letter order from above + std::string res; + for(size_t i = 0; i < s.size(); i++) { + char c = s[i]; + if(0 == i && ' ' == c) { + // First char is a space, must escape + res += "\\x20"; + } else if(s.size()-1 == i && ' ' == c) { + // Last char is a space, must escape + res += "\\x20"; + } else if(c < ' ') { + // It is a control character + auto i = escv.find(c); + if(std::string::npos != i) { + // One of the standard escapes + res += '\\'; + res += escc[i]; + } else { + // Make it a hex escape + res += fmt::format("\\x{:02x}", c & 0xff); + } + } else if('\\' == c) { + // A backslash + res += "\\\\"; + } else { + // Nothing special, just add + res += c; + } + } + return res; +} + int main(int argc, char * const argv[]) { int lose = 0; @@ -308,7 +344,7 @@ int main(int argc, char * const argv[]) std::cout << '[' << sect << ']'; std::cout << var.first; if(content) - std::cout << '=' << var.second; + std::cout << '=' << escapeString(var.second); std::cout << std::endl; } } @@ -319,7 +355,7 @@ int main(int argc, char * const argv[]) std::cout << '[' << section << ']'; std::cout << var.first; if(content) - std::cout << '=' << var.second; + std::cout << '=' << escapeString(var.second); std::cout << std::endl; } } diff --git a/tests/inifile/comments/expected b/tests/inifile/comments/expected index d51f7c967b6..9017aba0e96 100644 --- a/tests/inifile/comments/expected +++ b/tests/inifile/comments/expected @@ -1,4 +1,4 @@ value1 -value2 +value2 ; inline not comment value3 -value4 +value4 # another inline not comment diff --git a/tests/inifile/comments/with_comments.ini b/tests/inifile/comments/with_comments.ini index ec99f448e1b..b2bb26bfb98 100644 --- a/tests/inifile/comments/with_comments.ini +++ b/tests/inifile/comments/with_comments.ini @@ -2,7 +2,7 @@ [section1] key1=value1 ; Another comment -key2=value2 ; inline comment +key2=value2 ; inline not comment # different comment style key3=value3 -key4=value4 # another inline comment +key4=value4 # another inline not comment diff --git a/tests/inifile/ini_api_c/test.sh b/tests/inifile/ini_api_c/test.sh index 5285886b20a..182531b2dde 100755 --- a/tests/inifile/ini_api_c/test.sh +++ b/tests/inifile/ini_api_c/test.sh @@ -13,7 +13,7 @@ true > result r "+++ Running test program for 'initest' +++" ( echo "[SECTION]" - echo "VAR='value to check the rest is missing'" + echo "VAR=value to check the rest is missing" echo "VARdp=42" echo "VARxp=0x2a" echo "VARop=0o52" diff --git a/tests/inifile/inivalue/expected b/tests/inifile/inivalue/expected index 00bfd48f4cc..257b8d9c6be 100644 --- a/tests/inifile/inivalue/expected +++ b/tests/inifile/inivalue/expected @@ -170,33 +170,19 @@ error: Tilde expansion failed on '~/xyz' ini-file reproduction success --- comment removal processing xtest.ini:3: error: Invalid section. Missing ']' -blabla -blabla +blabla # comment not ignored +blabla ; comment not ignored --- string processing single/double quote -foo -bar ---- string merging -foobar -barfoo -foofoo ---- string quote escapes -foo s' d" -bar d" s' ---- string bad quote escapes -xtest.ini:3: warning: Improper escape of ' quote, ignored -foo \" -xtest.ini:3: warning: Improper escape of ' quote, ignored -bar ' ---- junk between string merge -xtest.ini:2: error: Expected single or double quote, got 'b' at position 7 of the value -xtest.ini:2: error: Expected single or double quote, got 'b' at position 7 of the value ---- escape fail at end-of-string -xtest.ini:2: error: Missing terminating " quote +'foo' +"bar" +--- string processing single/double in normal text +foo 'quote' bar +bar "quote" foo --- string continuations escaped newlines -some -string -separated on -lines +"some + string + separated on + lines" --- embedded nul in values xtest.ini:2: error: Embedded literal NUL character not supported xtest.ini:2: error: Embedded literal NUL character not supported diff --git a/tests/inifile/inivalue/test.sh b/tests/inifile/inivalue/test.sh index debe94ded4f..f510cc98e11 100755 --- a/tests/inifile/inivalue/test.sh +++ b/tests/inifile/inivalue/test.sh @@ -235,6 +235,14 @@ r "--- reproduce ini-file content" echo "VAR=0x2a" echo "VAR=0o52" echo "VAR=0b101010" + echo "[QUOTED]" + echo "S='\"single surrounding \\\\ double\"'" + echo "D=\"'double surrounding \\\\ single'\"" + echo "E=embedded \"double\" and 'single'" + echo "C=\" spaces and \\x1f newline\\n\"" + echo "U=\\x20 start spaces" + echo "V=end spaces \\x20" + echo "W=\\x20 all spaces \\x20" echo "[NOTQUINE]" echo "This=-42" echo "iS=-0x2a" @@ -258,8 +266,8 @@ r "--- comment removal processing" echo "[MUSTFAILHERE" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Must fail @line 3" ( echo " [SECTION] ; comment ignored" - echo " VARa=blabla # comment ignored" - echo " VARb=blabla ; comment ignored" + echo " VARa=blabla # comment not ignored" + echo " VARb=blabla ; comment not ignored" ) > xtest.ini tst --var=VARa --sec=SECTION || f "Comment VARa removal" tst --var=VARb --sec=SECTION || f "Comment VARb removal" @@ -271,49 +279,19 @@ r "--- string processing single/double quote" tst --var=VARs --sec=SECTION || f "Single quote string" tst --var=VARq --sec=SECTION || f "Double quote string" -r "--- string merging" +r "--- string processing single/double in normal text" ( echo "[SECTION]" - echo -e "VARs='foo' \t 'bar'" - echo -e "VARq=\"bar\" \t \"foo\"" - echo -e "VARc='foo' \t \"foo\"" ) > xtest.ini -tst --var=VARs --sec=SECTION || f "Single quote merge string" -tst --var=VARq --sec=SECTION || f "Double quote merge string" -tst --var=VARc --sec=SECTION || f "Mixed quote merge string" - -r "--- string quote escapes" -( echo "[SECTION]" - echo "VARs='foo s\\' d\"'" - echo "VARq=\"bar d\\\" s'\"" ) > xtest.ini -tst --var=VARs --sec=SECTION || f "Single quote escape string" -tst --var=VARq --sec=SECTION || f "Double quote escape string" - -r "--- string bad quote escapes" -( echo "[SECTION]" - echo "VARs='foo \\\" '" # This is _not_ improper, just literal backslash followed by double quote - echo "VARq=\"bar \\' \"" ) > xtest.ini -tst --var=VARs --sec=SECTION || f "Single quote non-escape string" -tst --var=VARq --sec=SECTION || f "Double quote bad escape string" - -r "--- junk between string merge" -( echo "[SECTION]" - echo "VAR='bar' bla 'bar'" ) > xtest.ini -tst --var=VAR --sec=SECTION && t "Junk between SQ string merge" - -( echo "[SECTION]" - echo "VAR=\"bar\" bla \"bar\"" ) > xtest.ini -tst --var=VAR --sec=SECTION && t "Junk between DQ string merge" - -r "--- escape fail at end-of-string" -( echo "[SECTION]" - echo "VAR=\"bar \\\"" ) > xtest.ini -tst --var=VAR --sec=SECTION && t "EOS escape" + echo "VARs=foo 'quote' bar" + echo "VARq=bar \"quote\" foo" ) > xtest.ini +tst --var=VARs --sec=SECTION || f "Embedded single quote string" +tst --var=VARq --sec=SECTION || f "Embedded double quote string" r "--- string continuations escaped newlines" ( echo "[SECTION]" - echo "VAR=\"some\n\" \\" - echo " \"string\n\" \\" - echo " \"separated on\n\" \\" - echo " \"lines\"" ) > xtest.ini + echo "VAR=\"some\n \\" + echo " string\n \\" + echo " separated on\n \\" + echo " lines\"" ) > xtest.ini tst --var=VAR --sec=SECTION || f "String collection and continuation" r "--- embedded nul in values" @@ -321,45 +299,45 @@ r "--- embedded nul in values" echo -e "VAR=val\0val" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Embedded NUL in value" ( echo "[SECTION]" - echo -e "VAR=\"val\0val\"" ) > xtest.ini + echo -e "VAR=val\0val" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Embedded literal NUL" ( echo "[SECTION]" - echo "VAR=\"\\0\"" ) > xtest.ini + echo "VAR=\\0" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Embedded octal NUL" ( echo "[SECTION]" - echo "VAR=\"\\x00\"" ) > xtest.ini + echo "VAR=\\x00" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Embedded hex NUL" ( echo "[SECTION]" - echo "VAR=\"\\u0000\"" ) > xtest.ini + echo "VAR=\\u0000" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Embedded UTF-16 NUL" ( echo "[SECTION]" - echo "VAR=\"\\U00000000\"" ) > xtest.ini + echo "VAR=\\U00000000" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Embedded UTF-32 NUL" r "--- escape invalid and improper hex" ( echo "[SECTION]" - echo "VAR=\"\\xYZ\"" ) > xtest.ini + echo "VAR=\\xYZ" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid hex escape" ( echo "[SECTION]" - echo "VAR=\"\\xY\"" ) > xtest.ini + echo "VAR=\\xY" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Improper short hex escape" ( echo "[SECTION]" echo "VAR=\"\\xY \"" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid short hex escape" ( echo "[SECTION]" - echo "VAR=\"\\uVWZY\"" ) > xtest.ini + echo "VAR=\\uVWZY" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid hex UTF-16" ( echo "[SECTION]" - echo "VAR=\"\\uVWZ\"" ) > xtest.ini + echo "VAR=\\uVWZ" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Improper short hex UTF-16" ( echo "[SECTION]" echo "VAR=\"\\uVWZ \"" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid short hex UTF-16" ( echo "[SECTION]" - echo "VAR=\"\\Uvwxyijkl\"" ) > xtest.ini + echo "VAR=\\Uvwxyijkl" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid hex UTF-32" ( echo "[SECTION]" - echo "VAR=\"\\Uvwxyijk\"" ) > xtest.ini + echo "VAR=\\Uvwxyijk" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Improper short hex UTF-32" ( echo "[SECTION]" echo "VAR=\"\\Uvwxyijk \"" ) > xtest.ini @@ -367,31 +345,31 @@ tst --var=VAR --sec=SECTION && t "Invalid short hex UTF-32" r "--- UTF-16 surrogates, sigh" ( echo "[SECTION]" - echo "VAR=\"\\ud800\"" ) > xtest.ini + echo "VAR=\\ud800" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Missing UTF-16 low surrogate" ( echo "[SECTION]" - echo "VAR=\"\\udc00\"" ) > xtest.ini + echo "VAR=\\udc00" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid UTF-16 high surrogate" ( echo "[SECTION]" - echo "VAR=\"\\ud800\\ud800\"" ) > xtest.ini + echo "VAR=\\ud800\\ud800" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid UTF-16 low surrogate" ( echo "[SECTION]" echo "VAR=\"\\ud800\\udc0 \"" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid UTF-16 low surrogate" ( echo "[SECTION]" # f600; smiley - echo "VAR=\"\\ud83d\\ude00\"" ) > xtest.ini + echo "VAR=\\ud83d\\ude00" ) > xtest.ini tst --var=VAR --sec=SECTION || f "Valid UTF-16" r "--- invalid code points" ( echo "[SECTION]" - echo "VAR=\"\\U00110000\"" ) > xtest.ini + echo "VAR=\\U00110000" ) > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid code point UTF-32" r "--- valid code points" ( echo "[SECTION]" # AAAA - echo "VAR=\"\\101\\x41\\u0041\\U00000041\"" ) > xtest.ini + echo "VAR=\\101\\x41\\u0041\\U00000041" ) > xtest.ini tst --var=VAR --sec=SECTION || f "Valid code points" r "--- invalid UTF-8" @@ -424,7 +402,7 @@ tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f8 code point" echo -e '[SECTION]\nVAR="\\xff "' > xtest.ini tst --var=VAR --sec=SECTION && t "Invalid UTF-8 ff code point" # f600; smiley: 11110 000 10 011111 10 011000 10 000000 -echo -e '[SECTION]\nVAR="\\xf0\\x9f\\x98\\x80"' > xtest.ini +echo -e '[SECTION]\nVAR=\\xf0\\x9f\\x98\\x80' > xtest.ini tst --var=VAR --sec=SECTION || f "UTF-8 Smiley" r "+++ all done +++" diff --git a/tests/inifile/python_bindings/expected b/tests/inifile/python_bindings/expected index 0a74a27bff4..c7f38677791 100644 --- a/tests/inifile/python_bindings/expected +++ b/tests/inifile/python_bindings/expected @@ -32,13 +32,13 @@ Real 3: 42.0 Real 4: -42.0 Real 5: None String 1: 'String without quotes' -String 2: 'String with double quotes' -String 3: 'String with single quotes' -String 4: 'String concat with mixed quotes and so' -String 5: '' -String 6: '' -String 7: ' ' -String 8: 'Smile! 😀😀😀' +String 2: '"String with double quotes"' +String 3: ''String with single quotes'' +String 4: '"String concat " "with" ' mixed ' 'quotes' " and so"' +String 5: '# not empty' +String 6: '"" # not empty too' +String 7: '" " # a space, don't know why you'd want that...' +String 8: '"Smile! 😀😀😀"' String 9: None Sections: -> section @@ -90,20 +90,20 @@ Variables of all sections: -> ('real', '-4.2e1') -> ('real', 'invalid') -> ('string', 'String without quotes') --> ('string', 'String with double quotes') --> ('string', 'String with single quotes') --> ('string', 'String concat with mixed quotes and so') --> ('string', '') --> ('string', '') --> ('string', ' ') --> ('string', 'Smile! 😀😀😀') +-> ('string', '"String with double quotes"') +-> ('string', "'String with single quotes'") +-> ('string', '"String concat " "with" \' mixed \' \'quotes\' " and so"') +-> ('string', '# not empty') +-> ('string', '"" # not empty too') +-> ('string', '" " # a space, don\'t know why you\'d want that...') +-> ('string', '"Smile! 😀😀😀"') -> ('key1', 'value1') -> ('key1', 'value2') -> ('key1', 'value3') -> ('key4', 'value4') Variables of non-existent section: [] values.ini:22: [integers]int='42' -values.ini:50: [strings]string='String with single quotes' +values.ini:50: [strings]string=''String with single quotes'' None:None: [missing]missing=None findall [section2]key1: value1, value2, value3 findall [section2]: value1, value2, value3, value4 diff --git a/tests/inifile/python_bindings/values.ini b/tests/inifile/python_bindings/values.ini index 8d5d4b68928..56d9a7c62a4 100644 --- a/tests/inifile/python_bindings/values.ini +++ b/tests/inifile/python_bindings/values.ini @@ -49,8 +49,8 @@ string = String without quotes string = "String with double quotes" string = 'String with single quotes' string = "String concat " "with" ' mixed ' 'quotes' " and so" -string = # empty -string = "" # empty too +string = # not empty +string = "" # not empty too string = " " # a space, don't know why you'd want that... string = "Smile! \U0001F600\ud83d\ude00\xf0\x9f\x98\x80"