浏览代码

Property parser: Allow single quotation marks, preserve whitespace in quotes

Now also allows empty strings in quotes.
Michael Ragazzon 1 月之前
父节点
当前提交
af8476c954
共有 2 个文件被更改,包括 84 次插入20 次删除
  1. 33 19
      Source/Core/PropertySpecification.cpp
  2. 51 1
      Tests/Source/UnitTests/PropertySpecification.cpp

+ 33 - 19
Source/Core/PropertySpecification.cpp

@@ -479,24 +479,33 @@ String PropertySpecification::PropertiesToString(const PropertyDictionary& dicti
 
 void PropertySpecification::ParsePropertyValues(StringList& values_list, const String& values, const SplitOption split_option) const
 {
+	RMLUI_ASSERT(values_list.empty());
+
 	const bool split_values = (split_option != SplitOption::None);
 	const bool split_by_comma = (split_option == SplitOption::Comma);
 	const bool split_by_whitespace = (split_option == SplitOption::Whitespace);
 
 	String value;
 
+	auto SubmitExactValue = [&]() {
+		values_list.push_back(std::move(value));
+		value.clear();
+	};
+
 	auto SubmitValue = [&]() {
 		value = StringUtilities::StripWhitespace(value);
-		if (value.size() > 0)
-		{
-			values_list.push_back(value);
-			value.clear();
-		}
+		if (!value.empty())
+			SubmitExactValue();
 	};
 
+	auto IsAllWhitespace = [](const String& string) { return std::all_of(string.begin(), string.end(), StringUtilities::IsWhitespace); };
+
+	auto Error = [&]() { values_list.clear(); };
+
 	enum ParseState { VALUE, VALUE_PARENTHESIS, VALUE_QUOTE, VALUE_QUOTE_ESCAPE_NEXT };
 	ParseState state = VALUE;
 	int open_parentheses = 0;
+	char open_quote_character = 0;
 	size_t character_index = 0;
 
 	while (character_index < values.size())
@@ -510,27 +519,28 @@ void PropertySpecification::ParsePropertyValues(StringList& values_list, const S
 		{
 			if (character == ';')
 			{
-				value = StringUtilities::StripWhitespace(value);
 				if (value.size() > 0)
 				{
 					values_list.push_back(value);
 					value.clear();
 				}
 			}
-			else if (split_by_comma ? (character == ',') : StringUtilities::IsWhitespace(character))
+			else if ((split_by_comma && character == ',') || (split_by_whitespace && StringUtilities::IsWhitespace(character)))
 			{
-				if (split_values)
-					SubmitValue();
-				else
-					value += character;
+				SubmitValue();
 			}
-			else if (character == '"')
+			else if (character == '"' || character == '\'')
 			{
 				state = VALUE_QUOTE;
+				open_quote_character = character;
 				if (split_by_whitespace)
 					SubmitValue();
+				else if (split_by_comma)
+					value += character;
+				else if (IsAllWhitespace(value))
+					value.clear();
 				else
-					value += (split_by_comma ? '"' : ' ');
+					return Error();
 			}
 			else if (character == '(')
 			{
@@ -556,9 +566,10 @@ void PropertySpecification::ParsePropertyValues(StringList& values_list, const S
 				if (open_parentheses == 0)
 					state = VALUE;
 			}
-			else if (character == '"')
+			else if (character == '"' || character == '\'')
 			{
 				state = VALUE_QUOTE;
+				open_quote_character = character;
 			}
 
 			value += character;
@@ -566,15 +577,15 @@ void PropertySpecification::ParsePropertyValues(StringList& values_list, const S
 		break;
 		case VALUE_QUOTE:
 		{
-			if (character == '"')
+			if (character == open_quote_character)
 			{
 				if (open_parentheses == 0)
 				{
 					state = VALUE;
-					if (split_by_whitespace)
-						SubmitValue();
+					if (split_by_comma)
+						value += character;
 					else
-						value += (split_by_comma ? '"' : ' ');
+						SubmitExactValue();
 				}
 				else
 				{
@@ -594,7 +605,7 @@ void PropertySpecification::ParsePropertyValues(StringList& values_list, const S
 		break;
 		case VALUE_QUOTE_ESCAPE_NEXT:
 		{
-			if (character == '"' || character == '\\')
+			if (character == '"' || character == '\'' || character == '\\')
 			{
 				value += character;
 			}
@@ -611,6 +622,9 @@ void PropertySpecification::ParsePropertyValues(StringList& values_list, const S
 
 	if (state == VALUE)
 		SubmitValue();
+
+	if (!split_values && values_list.size() > 1)
+		return Error();
 }
 
 } // namespace Rml

+ 51 - 1
Tests/Source/UnitTests/PropertySpecification.cpp

@@ -125,10 +125,21 @@ TEST_CASE("PropertySpecification.ParsePropertyValues")
 	Parse("none,,,red", {"none", "red"}, SplitOption::Comma);
 	Parse("none, ,  ,red", {"none", "red"}, SplitOption::Comma);
 
+	Parse(R"("name")", {R"("name")"}, SplitOption::Comma);
+	Parse(R"("name" none)", {R"("name" none)"}, SplitOption::Comma);
+	Parse(R"(none "name")", {R"(none "name")"}, SplitOption::Comma);
+	Parse(R"( none "name" )", {R"(none "name")"}, SplitOption::Comma);
+	Parse(R"( "name", red )", {R"("name")", R"(red)"}, SplitOption::Comma);
+	Parse(R"( "name", "red" )", {R"("name")", R"("red")"}, SplitOption::Comma);
+	Parse(R"( "name", none "red" )", {R"("name")", R"(none "red")"}, SplitOption::Comma);
+
 	Parse("\"string with spaces\"", "string with spaces");
 	Parse("\"string with spaces\" two", {"string with spaces", "two"});
 	Parse("\"string with spaces\"two", {"string with spaces", "two"});
-	Parse("\"string with spaces\"two", "string with spaces two", SplitOption::None);
+
+	Parse("\"string\"two", {}, SplitOption::None);
+	Parse("one\"string\"", {}, SplitOption::None);
+	Parse("one \"string\"", {}, SplitOption::None);
 
 	Parse("\"string (with) ((parenthesis\" two", {"string (with) ((parenthesis", "two"});
 	Parse("\"none,,red\" two", {"none,,red", "two"});
@@ -168,6 +179,45 @@ TEST_CASE("PropertySpecification.ParsePropertyValues")
 	Parse(R"(image("a\\\b"))", R"(image("a\\b"))");
 	Parse(R"(image("a\\\\b"))", R"(image("a\\b"))");
 
+	Parse(R"()", {});
+	Parse(R"("")", R"()");
+	Parse(R"(" ")", R"( )");
+	Parse(R"("abc")", R"(abc)");
+	Parse(R"( "abc" )", R"(abc)");
+	Parse(R"(" abc")", R"( abc)");
+	Parse(R"("abc ")", R"(abc )");
+	Parse(R"(" abc ")", R"( abc )");
+	Parse(R"("test"  none)", {R"(test)", R"(none)"});
+
+	Parse(R"('')", R"()");
+	Parse(R"(' ')", R"( )");
+	Parse(R"('abc')", R"(abc)");
+	Parse(R"( 'abc' )", R"(abc)");
+	Parse(R"(' abc')", R"( abc)");
+	Parse(R"('abc ')", R"(abc )");
+	Parse(R"(' abc ')", R"( abc )");
+	Parse(R"('test'  none)", {R"(test)", R"(none)"});
+
+	Parse(R"()", {}, SplitOption::None);
+	Parse(R"("")", R"()", SplitOption::None);
+	Parse(R"(" ")", R"( )", SplitOption::None);
+	Parse(R"("abc")", R"(abc)", SplitOption::None);
+	Parse(R"( "abc" )", R"(abc)", SplitOption::None);
+	Parse(R"(" abc")", R"( abc)", SplitOption::None);
+	Parse(R"("abc ")", R"(abc )", SplitOption::None);
+	Parse(R"(" abc ")", R"( abc )", SplitOption::None);
+	Parse(R"("test"  none)", {}, SplitOption::None);
+
+	Parse(R"("","")", {R"("")", R"("")"}, SplitOption::Comma);
+	Parse(R"(" "," ")", {R"(" ")", R"(" ")"}, SplitOption::Comma);
+	Parse(R"(" " , " ")", {R"(" ")", R"(" ")"}, SplitOption::Comma);
+	Parse(R"( " " none, yes)", {R"(" " none)", R"(yes)"}, SplitOption::Comma);
+
+	Parse(R"('','')", {R"('')", R"('')"}, SplitOption::Comma);
+	Parse(R"(' ',' ')", {R"(' ')", R"(' ')"}, SplitOption::Comma);
+	Parse(R"(' ' , ' ')", {R"(' ')", R"(' ')"}, SplitOption::Comma);
+	Parse(R"( ' ' none, yes)", {R"(' ' none)", R"(yes)"}, SplitOption::Comma);
+
 	Rml::Shutdown();
 }