diff --git a/core/os/file_access.cpp b/core/os/file_access.cpp index f5fbac14f2d..a362d0f72bb 100644 --- a/core/os/file_access.cpp +++ b/core/os/file_access.cpp @@ -315,52 +315,53 @@ String FileAccess::get_line() const { } Vector FileAccess::get_csv_line(const String &p_delim) const { - ERR_FAIL_COND_V(p_delim.length() != 1, Vector()); + ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector(), "Only single character delimiters are supported to parse CSV lines."); + ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines."); - String l; + String line; + + // CSV can support entries with line breaks as long as they are enclosed + // in double quotes. So our "line" might be more than a single line in the + // text file. int qc = 0; do { if (eof_reached()) { break; } - - l += get_line() + "\n"; + line += get_line() + "\n"; qc = 0; - for (int i = 0; i < l.length(); i++) { - if (l[i] == '"') { + for (int i = 0; i < line.length(); i++) { + if (line[i] == '"') { qc++; } } - } while (qc % 2); - l = l.substr(0, l.length() - 1); + // Remove the extraneous newline we've added above. + line = line.substr(0, line.length() - 1); Vector strings; bool in_quote = false; String current; - for (int i = 0; i < l.length(); i++) { - CharType c = l[i]; - CharType s[2] = { 0, 0 }; - + for (int i = 0; i < line.length(); i++) { + CharType c = line[i]; + // A delimiter ends the current entry, unless it's in a quoted string. if (!in_quote && c == p_delim[0]) { strings.push_back(current); current = String(); } else if (c == '"') { - if (l[i + 1] == '"' && in_quote) { - s[0] = '"'; - current += s; + // Doubled quotes are escapes for intentional quotes in the string. + if (line[i + 1] == '"' && in_quote) { + current += '"'; i++; } else { in_quote = !in_quote; } } else { - s[0] = c; - current += s; + current += c; } } - strings.push_back(current); return strings; diff --git a/doc/classes/File.xml b/doc/classes/File.xml index 88fd15c86ef..84bcad41c1f 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -99,8 +99,15 @@ - Returns the next value of the file in CSV (Comma-Separated Values) format. You can pass a different delimiter [code]delim[/code] to use other than the default [code]","[/code] (comma). This delimiter must be one-character long. - Text is interpreted as being UTF-8 encoded. + Returns the next value of the file in CSV (Comma-Separated Values) format. You can pass a different delimiter [code]delim[/code] to use other than the default [code]","[/code] (comma). This delimiter must be one-character long, and cannot be a double quotation mark. + Text is interpreted as being UTF-8 encoded. Text values must be enclosed in double quotes if they include the delimiter character. Double quotes within a text value can be escaped by doubling their occurrence. + For example, the following CSV lines are valid and will be properly parsed as two strings each: + [codeblock] + Alice,"Hello, Bob!" + Bob,Alice! What a surprise! + Alice,"I thought you'd reply with ""Hello, world""." + [/codeblock] + Note how the second line can omit the enclosing quotes as it does not include the delimiter. However it [i]could[/i] very well use quotes, it was only written without for demonstration purposes. The third line must use [code]""[/code] for each quotation mark that needs to be interpreted as such instead of the end of a text value.