prepare for multiple and configurable key-value separators (e.g. key=value and key:value) v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Thu, 26 Nov 2020 18:28:27 +0100
branchv_0
changeset 20 9187f0439ca9
parent 19 967f73af64a4
child 21 b35baebf5005
prepare for multiple and configurable key-value separators (e.g. key=value and key:value)
src/lib/INIReader.cpp
--- a/src/lib/INIReader.cpp	Thu Nov 26 17:08:49 2020 +0100
+++ b/src/lib/INIReader.cpp	Thu Nov 26 18:28:27 2020 +0100
@@ -70,6 +70,18 @@
 	 */
 	bool allowSubKeys = true;
 
+	/**
+	 * This might be configurable.
+	 * 
+	 * Classic INI uses „key=value“ syntax.
+	 * But some other formats/dialects might use key:value.
+	 * 
+	 * Only single character separators are supported.
+	 * If multiple separators should be recognized (e.g. both „=“ and „:“), this string will contain all of them,
+	 * i.e. „:=“ does not mean that the „key:=value“ syntax, but „key=value“ or „key:value“.
+	 */
+	std::string keyValueSeparators = "=";
+
 	int lineNumber = 1;
 	int eventNumber = 0;
 
@@ -115,26 +127,31 @@
 		else result.put('\n');
 	}
 
-	std::string readUntil(char until, bool* found = nullptr) {
+	std::string readUntil(const char until, bool* found = nullptr) {
+		return readUntil(std::string(1, until), found);
+	}
+
+	std::string readUntil(const std::string& until, bool* found = nullptr) {
 		std::stringstream result;
 
-		for (char ch = peek(); input.good() && ch != until; ch = peek()) {
+		for (char ch = peek(); input.good() && !oneOf(ch, until); ch = peek()) {
 			if (ch == '\\') {
 				get();
 				ch = get();
-				if (ch == until && ch == '\n') processContinuingLine(result);
-				else if (ch == until) result.put(ch);
+				if (oneOf(ch, until) && ch == '\n') processContinuingLine(result);
+				else if (oneOf(ch, until)) result.put(ch);
 				else if (ch == std::istream::traits_type::eof()) break;
 				else result.put('\\').put(ch);
-				// TODO: two-stage and modular unescaping: here unescape only \+LF or more genereally: unescape only the until character and rest leave untouched
-				// second escaping stage move to separate class/wrapper (similar to hierarchical wrappers)
+				// unescaping is done in two phases:
+				// here we unescape just the \n (LF)
+				// other escape sequences are leaved untouched and will be processed in later phases, see see UnescapingINIHandler
 			} else {
 				ch = get();
 				result.put(ch);
 			}
 		}
 
-		if (peek() == until) {
+		if (oneOf(peek(), until)) {
 			get();
 			if (found) *found = true;
 		} else {
@@ -144,13 +161,17 @@
 		return result.str();
 	}
 
-	std::string readToken(char until, char* quote = nullptr, bool* found = nullptr) {
+	std::string readToken(const char until, char* quote = nullptr, bool* found = nullptr) {
+		return readToken(std::string(1, until), quote, found);
+	}
+
+	std::string readToken(const std::string& until, char* quote = nullptr, bool* found = nullptr) {
 		std::string result;
 
 		char ch = peek();
 		if (isQuote(ch)) {
 			if (quote) *quote = ch;
-			result = readUntil(get(), found);
+			result = readUntil(std::string(1, get()), found);
 		} else {
 			if (quote) *quote = 0;
 			result = readUntil(until, found);
@@ -160,10 +181,14 @@
 	}
 
 	std::string readTokenAndEatTerminator(char until, char* quote = nullptr, bool* found = nullptr) {
+		return readTokenAndEatTerminator(std::string(1, until), quote, found);
+	}
+
+	std::string readTokenAndEatTerminator(const std::string& until, char* quote = nullptr, bool* found = nullptr) {
 		std::string result = readToken(until, quote, found);
 		if (*quote) {
 			readAllWhitespace();
-			if (get() != until) throw std::logic_error(std::string("missing „") + std::string(1, until) + "“ after quoted section name");
+			if (!oneOf(get(), until)) throw std::logic_error(std::string("missing „") + until + "“ after quoted section name");
 		}
 		return result;
 	}
@@ -176,6 +201,15 @@
 		return ch == '"' || ch == '\'';
 	}
 
+	/**
+	 * @param ch character to be evaluated
+	 * @param options list of options (characters)
+	 * @return whether ch is one of options
+	 */
+	bool oneOf(char ch, const std::string& options) {
+		return options.find(ch) != std::string::npos;
+	}
+
 	std::string trim(std::string s) {
 		return std::regex_replace(s, std::regex("^\\s+|\\s+$"), "");
 	}
@@ -255,14 +289,14 @@
 				event.lineNumber = lineNumber;
 				event.eventNumber = ++eventNumber;
 
-				std::string fullKey = readToken('=', &quote, &found);
+				std::string fullKey = readToken(keyValueSeparators, &quote, &found);
 				if (!found) throw std::logic_error(std::string("missing = after key: '") + fullKey + "'");
 				if (!quote) fullKey = trim(fullKey);
 				readSpacesAndTabs();
 
 				if (quote) {
 					ch = get();
-					if (ch == '=') readSpacesAndTabs();
+					if (oneOf(ch, keyValueSeparators)) readSpacesAndTabs();
 					else throw std::logic_error(std::string("missing = after quoted key: '") + fullKey + "'");
 				}