From 674050f1a955e5e0bd161cc2f6afa06b0048a1fa Mon Sep 17 00:00:00 2001 From: Dakror Date: Thu, 20 May 2021 14:20:27 +0200 Subject: [PATCH 1/4] Ignore curly brackets inside string literals in data expressions --- Source/Core/BaseXMLParser.cpp | 1163 ++++++++++++------------ Source/Core/Factory.cpp | 17 +- Source/Core/XMLParseTools.h | 144 +-- Tests/Source/UnitTests/DataBinding.cpp | 51 ++ 4 files changed, 720 insertions(+), 655 deletions(-) diff --git a/Source/Core/BaseXMLParser.cpp b/Source/Core/BaseXMLParser.cpp index 0d792df48..ad1aacd91 100644 --- a/Source/Core/BaseXMLParser.cpp +++ b/Source/Core/BaseXMLParser.cpp @@ -1,578 +1,585 @@ -/* - * This source file is part of RmlUi, the HTML/CSS Interface Middleware - * - * For the latest information, see http://github.com/mikke89/RmlUi - * - * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd - * Copyright (c) 2019 The RmlUi Team, and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#include "../../Include/RmlUi/Core/BaseXMLParser.h" -#include "../../Include/RmlUi/Core/Profiling.h" -#include "../../Include/RmlUi/Core/Stream.h" -#include "XMLParseTools.h" -#include - -namespace Rml { - -BaseXMLParser::BaseXMLParser() -{} - -BaseXMLParser::~BaseXMLParser() -{} - -// Registers a tag as containing general character data. -void BaseXMLParser::RegisterCDATATag(const String& tag) -{ - if (!tag.empty()) - cdata_tags.insert(StringUtilities::ToLower(tag)); -} - -void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name) -{ - attributes_for_inner_xml_data.insert(attribute_name); -} - -// Parses the given stream as an XML file, and calls the handlers when -// interesting phenomenon are encountered. -void BaseXMLParser::Parse(Stream* stream) -{ - source_url = &stream->GetSourceURL(); - - xml_source.clear(); - - // We read in the whole XML file here. - // TODO: It doesn't look like the Stream interface is used for anything useful. We - // might as well just use a span or StringView, and get completely rid of it. - // @performance Otherwise, use the temporary allocator. - const size_t source_size = stream->Length(); - stream->Read(xml_source, source_size); - - xml_index = 0; - line_number = 1; - line_number_open_tag = 1; - - inner_xml_data = false; - inner_xml_data_terminate_depth = 0; - inner_xml_data_index_begin = 0; - - // Read (er ... skip) the header, if one exists. - ReadHeader(); - // Read the XML body. - ReadBody(); - - xml_source.clear(); - source_url = nullptr; -} - -// Get the current file line number -int BaseXMLParser::GetLineNumber() const -{ - return line_number; -} - -int BaseXMLParser::GetLineNumberOpenTag() const -{ - return line_number_open_tag; -} - -// Called when the parser finds the beginning of an element tag. -void BaseXMLParser::HandleElementStart(const String& RMLUI_UNUSED_PARAMETER(name), const XMLAttributes& RMLUI_UNUSED_PARAMETER(attributes)) -{ - RMLUI_UNUSED(name); - RMLUI_UNUSED(attributes); -} - -// Called when the parser finds the end of an element tag. -void BaseXMLParser::HandleElementEnd(const String& RMLUI_UNUSED_PARAMETER(name)) -{ - RMLUI_UNUSED(name); -} - -// Called when the parser encounters data. -void BaseXMLParser::HandleData(const String& RMLUI_UNUSED_PARAMETER(data), XMLDataType RMLUI_UNUSED_PARAMETER(type)) -{ - RMLUI_UNUSED(data); - RMLUI_UNUSED(type); -} - -/// Returns the source URL of this parse. Only valid during parsing. - -const URL* BaseXMLParser::GetSourceURLPtr() const -{ - return source_url; -} - -void BaseXMLParser::Next() { - xml_index += 1; -} - -bool BaseXMLParser::AtEnd() const { - return xml_index >= xml_source.size(); -} - -char BaseXMLParser::Look() const { - RMLUI_ASSERT(!AtEnd()); - return xml_source[xml_index]; -} - -void BaseXMLParser::HandleElementStartInternal(const String& name, const XMLAttributes& attributes) -{ - line_number_open_tag = line_number; - if (!inner_xml_data) - HandleElementStart(name, attributes); -} - -void BaseXMLParser::HandleElementEndInternal(const String& name) -{ - if (!inner_xml_data) - HandleElementEnd(name); -} - -void BaseXMLParser::HandleDataInternal(const String& data, XMLDataType type) -{ - if (!inner_xml_data) - HandleData(data, type); -} - -void BaseXMLParser::ReadHeader() -{ - if (PeekString("", temp); - } -} - -void BaseXMLParser::ReadBody() -{ - RMLUI_ZoneScoped; - - open_tag_depth = 0; - line_number_open_tag = 0; - - for(;;) - { - // Find the next open tag. - if (!FindString("<", data, true)) - break; - - const size_t xml_index_tag = xml_index - 1; - - // Check what kind of tag this is. - if (PeekString("!--")) - { - // Comment. - String temp; - if (!FindString("-->", temp)) - break; - } - else if (PeekString("![CDATA[")) - { - // CDATA tag; read everything (including markup) until the ending - // CDATA tag. - if (!ReadCDATA()) - break; - } - else if (PeekString("/")) - { - if (!ReadCloseTag(xml_index_tag)) - break; - - // Bail if we've hit the end of the XML data. - if (open_tag_depth == 0) - break; - } - else - { - if (!ReadOpenTag()) - break; - } - } - - // Check for error conditions - if (open_tag_depth > 0) - { - Log::Message(Log::LT_WARNING, "XML parse error on line %d of %s.", GetLineNumber(), source_url->GetURL().c_str()); - } -} - -bool BaseXMLParser::ReadOpenTag() -{ - // Increase the open depth - open_tag_depth++; - - // Opening tag; send data immediately and open the tag. - if (!data.empty()) - { - HandleDataInternal(data, XMLDataType::Text); - data.clear(); - } - - String tag_name; - if (!FindWord(tag_name, "/>")) - return false; - - bool section_opened = false; - - if (PeekString(">")) - { - // Simple open tag. - HandleElementStartInternal(tag_name, XMLAttributes()); - section_opened = true; - } - else if (PeekString("/") && - PeekString(">")) - { - // Empty open tag. - HandleElementStartInternal(tag_name, XMLAttributes()); - HandleElementEndInternal(tag_name); - - // Tag immediately closed, reduce count - open_tag_depth--; - } - else - { - // It appears we have some attributes. Let's parse them. - bool parse_inner_xml_as_data = false; - XMLAttributes attributes; - if (!ReadAttributes(attributes, parse_inner_xml_as_data)) - return false; - - if (PeekString(">")) - { - HandleElementStartInternal(tag_name, attributes); - section_opened = true; - } - else if (PeekString("/") && - PeekString(">")) - { - HandleElementStartInternal(tag_name, attributes); - HandleElementEndInternal(tag_name); - - // Tag immediately closed, reduce count - open_tag_depth--; - } - else - { - return false; - } - - if (section_opened && parse_inner_xml_as_data && !inner_xml_data) - { - inner_xml_data = true; - inner_xml_data_terminate_depth = open_tag_depth; - inner_xml_data_index_begin = xml_index; - } - } - - // Check if this tag needs to be processed as CDATA. - if (section_opened) - { - const String lcase_tag_name = StringUtilities::ToLower(tag_name); - bool is_cdata_tag = (cdata_tags.find(lcase_tag_name) != cdata_tags.end()); - - if (is_cdata_tag) - { - if (ReadCDATA(lcase_tag_name.c_str())) - { - open_tag_depth--; - if (!data.empty()) - { - HandleDataInternal(data, XMLDataType::CData); - data.clear(); - } - HandleElementEndInternal(tag_name); - - return true; - } - - return false; - } - } - - return true; -} - -bool BaseXMLParser::ReadCloseTag(const size_t xml_index_tag) -{ - if (inner_xml_data && open_tag_depth == inner_xml_data_terminate_depth) - { - // Closing the tag that initiated the inner xml data parsing. Set all its contents as Data to be - // submitted next, and disable the mode to resume normal parsing behavior. - RMLUI_ASSERT(inner_xml_data_index_begin <= xml_index_tag); - inner_xml_data = false; - data = xml_source.substr(inner_xml_data_index_begin, xml_index_tag - inner_xml_data_index_begin); - HandleDataInternal(data, XMLDataType::InnerXML); - data.clear(); - } - - // Closing tag; send data immediately and close the tag. - if (!data.empty()) - { - HandleDataInternal(data, XMLDataType::Text); - data.clear(); - } - - String tag_name; - if (!FindString(">", tag_name)) - return false; - - HandleElementEndInternal(StringUtilities::StripWhitespace(tag_name)); - - - // Tag closed, reduce count - open_tag_depth--; - - - return true; -} - -bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes, bool& parse_raw_xml_content) -{ - for (;;) - { - String attribute; - String value; - - // Get the attribute name - if (!FindWord(attribute, "=/>")) - { - return false; - } - - // Check if theres an assigned value - if (PeekString("=")) - { - if (PeekString("\"")) - { - if (!FindString("\"", value)) - return false; - } - else if (PeekString("'")) - { - if (!FindString("'", value)) - return false; - } - else if (!FindWord(value, "/>")) - { - return false; - } - } - - if (attributes_for_inner_xml_data.count(attribute) == 1) - parse_raw_xml_content = true; - - attributes[attribute] = StringUtilities::DecodeRml(value); - - // Check for the end of the tag. - if (PeekString("/", false) || PeekString(">", false)) - return true; - } -} - -bool BaseXMLParser::ReadCDATA(const char* tag_terminator) -{ - String cdata; - if (tag_terminator == nullptr) - { - FindString("]]>", cdata); - data += cdata; - return true; - } - else - { - for (;;) - { - // Search for the next tag opening. - if (!FindString("<", cdata)) - return false; - - if (PeekString("/", false)) - { - String tag; - if (FindString(">", tag)) - { - size_t slash_pos = tag.find('/'); - String tag_name = StringUtilities::StripWhitespace(slash_pos == String::npos ? tag : tag.substr(slash_pos + 1)); - if (StringUtilities::ToLower(tag_name) == tag_terminator) - { - data += cdata; - return true; - } - else - { - cdata += '<' + tag + '>'; - } - } - else - cdata += "<"; - } - else - cdata += "<"; - } - } -} - -// Reads from the stream until a complete word is found. -bool BaseXMLParser::FindWord(String& word, const char* terminators) -{ - while (!AtEnd()) - { - char c = Look(); - - // Count line numbers - if (c == '\n') - { - line_number++; - } - - // Ignore white space - if (StringUtilities::IsWhitespace(c)) - { - if (word.empty()) - { - Next(); - continue; - } - else - return true; - } - - // Check for termination condition - if (terminators && strchr(terminators, c)) - { - return !word.empty(); - } - - word += c; - Next(); - } - - return false; -} - -// Reads from the stream until the given character set is found. -bool BaseXMLParser::FindString(const char* string, String& data, bool escape_brackets) -{ - int index = 0; - bool in_brackets = false; - char previous = 0; - - while (string[index]) - { - if (AtEnd()) - return false; - - const char c = Look(); - - // Count line numbers - if (c == '\n') - { - line_number++; - } - - if(escape_brackets) - { - const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, c, previous); - if (error_str) - { - Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str); - return false; - } - } - - if (c == string[index] && !in_brackets) - { - index += 1; - } - else - { - if (index > 0) - { - data += String(string, index); - index = 0; - } - - data += c; - } - - previous = c; - Next(); - } - - return true; -} - -// Returns true if the next sequence of characters in the stream matches the -// given string. -bool BaseXMLParser::PeekString(const char* string, bool consume) -{ - const size_t start_index = xml_index; - const int start_line = line_number; - bool success = true; - int i = 0; - while (string[i]) - { - if (AtEnd()) - { - success = false; - break; - } - - const char c = Look(); - - // Count line numbers - if (c == '\n') - { - line_number++; - } - - // Seek past all the whitespace if we haven't hit the initial character yet. - if (i == 0 && StringUtilities::IsWhitespace(c)) - { - Next(); - } - else - { - if (c != string[i]) - { - success = false; - break; - } - - i++; - Next(); - } - } - - // Set the index to the start index unless we are consuming. - if (!consume || !success) - { - xml_index = start_index; - line_number = start_line; - } - - return success; -} - -} // namespace Rml +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "../../Include/RmlUi/Core/BaseXMLParser.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/Stream.h" +#include "XMLParseTools.h" +#include + +namespace Rml { + +BaseXMLParser::BaseXMLParser() +{} + +BaseXMLParser::~BaseXMLParser() +{} + +// Registers a tag as containing general character data. +void BaseXMLParser::RegisterCDATATag(const String& tag) +{ + if (!tag.empty()) + cdata_tags.insert(StringUtilities::ToLower(tag)); +} + +void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name) +{ + attributes_for_inner_xml_data.insert(attribute_name); +} + +// Parses the given stream as an XML file, and calls the handlers when +// interesting phenomenon are encountered. +void BaseXMLParser::Parse(Stream* stream) +{ + source_url = &stream->GetSourceURL(); + + xml_source.clear(); + + // We read in the whole XML file here. + // TODO: It doesn't look like the Stream interface is used for anything useful. We + // might as well just use a span or StringView, and get completely rid of it. + // @performance Otherwise, use the temporary allocator. + const size_t source_size = stream->Length(); + stream->Read(xml_source, source_size); + + xml_index = 0; + line_number = 1; + line_number_open_tag = 1; + + inner_xml_data = false; + inner_xml_data_terminate_depth = 0; + inner_xml_data_index_begin = 0; + + // Read (er ... skip) the header, if one exists. + ReadHeader(); + // Read the XML body. + ReadBody(); + + xml_source.clear(); + source_url = nullptr; +} + +// Get the current file line number +int BaseXMLParser::GetLineNumber() const +{ + return line_number; +} + +int BaseXMLParser::GetLineNumberOpenTag() const +{ + return line_number_open_tag; +} + +// Called when the parser finds the beginning of an element tag. +void BaseXMLParser::HandleElementStart(const String& RMLUI_UNUSED_PARAMETER(name), const XMLAttributes& RMLUI_UNUSED_PARAMETER(attributes)) +{ + RMLUI_UNUSED(name); + RMLUI_UNUSED(attributes); +} + +// Called when the parser finds the end of an element tag. +void BaseXMLParser::HandleElementEnd(const String& RMLUI_UNUSED_PARAMETER(name)) +{ + RMLUI_UNUSED(name); +} + +// Called when the parser encounters data. +void BaseXMLParser::HandleData(const String& RMLUI_UNUSED_PARAMETER(data), XMLDataType RMLUI_UNUSED_PARAMETER(type)) +{ + RMLUI_UNUSED(data); + RMLUI_UNUSED(type); +} + +/// Returns the source URL of this parse. Only valid during parsing. + +const URL* BaseXMLParser::GetSourceURLPtr() const +{ + return source_url; +} + +void BaseXMLParser::Next() { + xml_index += 1; +} + +bool BaseXMLParser::AtEnd() const { + return xml_index >= xml_source.size(); +} + +char BaseXMLParser::Look() const { + RMLUI_ASSERT(!AtEnd()); + return xml_source[xml_index]; +} + +void BaseXMLParser::HandleElementStartInternal(const String& name, const XMLAttributes& attributes) +{ + line_number_open_tag = line_number; + if (!inner_xml_data) + HandleElementStart(name, attributes); +} + +void BaseXMLParser::HandleElementEndInternal(const String& name) +{ + if (!inner_xml_data) + HandleElementEnd(name); +} + +void BaseXMLParser::HandleDataInternal(const String& data, XMLDataType type) +{ + if (!inner_xml_data) + HandleData(data, type); +} + +void BaseXMLParser::ReadHeader() +{ + if (PeekString("", temp); + } +} + +void BaseXMLParser::ReadBody() +{ + RMLUI_ZoneScoped; + + open_tag_depth = 0; + line_number_open_tag = 0; + + for(;;) + { + // Find the next open tag. + if (!FindString("<", data, true)) + break; + + const size_t xml_index_tag = xml_index - 1; + + // Check what kind of tag this is. + if (PeekString("!--")) + { + // Comment. + String temp; + if (!FindString("-->", temp)) + break; + } + else if (PeekString("![CDATA[")) + { + // CDATA tag; read everything (including markup) until the ending + // CDATA tag. + if (!ReadCDATA()) + break; + } + else if (PeekString("/")) + { + if (!ReadCloseTag(xml_index_tag)) + break; + + // Bail if we've hit the end of the XML data. + if (open_tag_depth == 0) + break; + } + else + { + if (!ReadOpenTag()) + break; + } + } + + // Check for error conditions + if (open_tag_depth > 0) + { + Log::Message(Log::LT_WARNING, "XML parse error on line %d of %s.", GetLineNumber(), source_url->GetURL().c_str()); + } +} + +bool BaseXMLParser::ReadOpenTag() +{ + // Increase the open depth + open_tag_depth++; + + // Opening tag; send data immediately and open the tag. + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::Text); + data.clear(); + } + + String tag_name; + if (!FindWord(tag_name, "/>")) + return false; + + bool section_opened = false; + + if (PeekString(">")) + { + // Simple open tag. + HandleElementStartInternal(tag_name, XMLAttributes()); + section_opened = true; + } + else if (PeekString("/") && + PeekString(">")) + { + // Empty open tag. + HandleElementStartInternal(tag_name, XMLAttributes()); + HandleElementEndInternal(tag_name); + + // Tag immediately closed, reduce count + open_tag_depth--; + } + else + { + // It appears we have some attributes. Let's parse them. + bool parse_inner_xml_as_data = false; + XMLAttributes attributes; + if (!ReadAttributes(attributes, parse_inner_xml_as_data)) + return false; + + if (PeekString(">")) + { + HandleElementStartInternal(tag_name, attributes); + section_opened = true; + } + else if (PeekString("/") && + PeekString(">")) + { + HandleElementStartInternal(tag_name, attributes); + HandleElementEndInternal(tag_name); + + // Tag immediately closed, reduce count + open_tag_depth--; + } + else + { + return false; + } + + if (section_opened && parse_inner_xml_as_data && !inner_xml_data) + { + inner_xml_data = true; + inner_xml_data_terminate_depth = open_tag_depth; + inner_xml_data_index_begin = xml_index; + } + } + + // Check if this tag needs to be processed as CDATA. + if (section_opened) + { + const String lcase_tag_name = StringUtilities::ToLower(tag_name); + bool is_cdata_tag = (cdata_tags.find(lcase_tag_name) != cdata_tags.end()); + + if (is_cdata_tag) + { + if (ReadCDATA(lcase_tag_name.c_str())) + { + open_tag_depth--; + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::CData); + data.clear(); + } + HandleElementEndInternal(tag_name); + + return true; + } + + return false; + } + } + + return true; +} + +bool BaseXMLParser::ReadCloseTag(const size_t xml_index_tag) +{ + if (inner_xml_data && open_tag_depth == inner_xml_data_terminate_depth) + { + // Closing the tag that initiated the inner xml data parsing. Set all its contents as Data to be + // submitted next, and disable the mode to resume normal parsing behavior. + RMLUI_ASSERT(inner_xml_data_index_begin <= xml_index_tag); + inner_xml_data = false; + data = xml_source.substr(inner_xml_data_index_begin, xml_index_tag - inner_xml_data_index_begin); + HandleDataInternal(data, XMLDataType::InnerXML); + data.clear(); + } + + // Closing tag; send data immediately and close the tag. + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::Text); + data.clear(); + } + + String tag_name; + if (!FindString(">", tag_name)) + return false; + + HandleElementEndInternal(StringUtilities::StripWhitespace(tag_name)); + + + // Tag closed, reduce count + open_tag_depth--; + + + return true; +} + +bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes, bool& parse_raw_xml_content) +{ + for (;;) + { + String attribute; + String value; + + // Get the attribute name + if (!FindWord(attribute, "=/>")) + { + return false; + } + + // Check if theres an assigned value + if (PeekString("=")) + { + if (PeekString("\"")) + { + if (!FindString("\"", value)) + return false; + } + else if (PeekString("'")) + { + if (!FindString("'", value)) + return false; + } + else if (!FindWord(value, "/>")) + { + return false; + } + } + + if (attributes_for_inner_xml_data.count(attribute) == 1) + parse_raw_xml_content = true; + + attributes[attribute] = StringUtilities::DecodeRml(value); + + // Check for the end of the tag. + if (PeekString("/", false) || PeekString(">", false)) + return true; + } +} + +bool BaseXMLParser::ReadCDATA(const char* tag_terminator) +{ + String cdata; + if (tag_terminator == nullptr) + { + FindString("]]>", cdata); + data += cdata; + return true; + } + else + { + for (;;) + { + // Search for the next tag opening. + if (!FindString("<", cdata)) + return false; + + if (PeekString("/", false)) + { + String tag; + if (FindString(">", tag)) + { + size_t slash_pos = tag.find('/'); + String tag_name = StringUtilities::StripWhitespace(slash_pos == String::npos ? tag : tag.substr(slash_pos + 1)); + if (StringUtilities::ToLower(tag_name) == tag_terminator) + { + data += cdata; + return true; + } + else + { + cdata += '<' + tag + '>'; + } + } + else + cdata += "<"; + } + else + cdata += "<"; + } + } +} + +// Reads from the stream until a complete word is found. +bool BaseXMLParser::FindWord(String& word, const char* terminators) +{ + while (!AtEnd()) + { + char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + // Ignore white space + if (StringUtilities::IsWhitespace(c)) + { + if (word.empty()) + { + Next(); + continue; + } + else + return true; + } + + // Check for termination condition + if (terminators && strchr(terminators, c)) + { + return !word.empty(); + } + + word += c; + Next(); + } + + return false; +} + +// Reads from the stream until the given character set is found. +bool BaseXMLParser::FindString(const char* string, String& data, bool escape_brackets) +{ + int index = 0; + bool in_brackets = false; + bool in_string = false; + char previous = 0; + + while (string[index]) + { + if (AtEnd()) + return false; + + const char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + if(escape_brackets) + { + if (in_brackets && c == '\'') { + in_string = !in_string; + } + + if (!in_string) { + const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str); + return false; + } + } + } + + if (c == string[index] && !in_brackets) + { + index += 1; + } + else + { + if (index > 0) + { + data += String(string, index); + index = 0; + } + + data += c; + } + + previous = c; + Next(); + } + + return true; +} + +// Returns true if the next sequence of characters in the stream matches the +// given string. +bool BaseXMLParser::PeekString(const char* string, bool consume) +{ + const size_t start_index = xml_index; + const int start_line = line_number; + bool success = true; + int i = 0; + while (string[i]) + { + if (AtEnd()) + { + success = false; + break; + } + + const char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + // Seek past all the whitespace if we haven't hit the initial character yet. + if (i == 0 && StringUtilities::IsWhitespace(c)) + { + Next(); + } + else + { + if (c != string[i]) + { + success = false; + break; + } + + i++; + Next(); + } + } + + // Set the index to the start index unless we are consuming. + if (!consume || !success) + { + xml_index = start_index; + line_number = start_line; + } + + return success; +} + +} // namespace Rml diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 22484f6cf..2bafc1878 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -400,14 +400,21 @@ bool Factory::InstanceElementText(Element* parent, const String& in_text) bool has_data_expression = false; bool inside_brackets = false; + bool inside_string = false; char previous = 0; for (const char c : text) { - const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, c, previous); - if (error_str) - { - Log::Message(Log::LT_WARNING, "Failed to instance text element '%s'. %s", text.c_str(), error_str); - return false; + if (inside_brackets && c == '\'') { + inside_string = !inside_string; + } + + if (!inside_string) { + const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "Failed to instance text element '%s'. %s", text.c_str(), error_str); + return false; + } } if (inside_brackets) diff --git a/Source/Core/XMLParseTools.h b/Source/Core/XMLParseTools.h index 1887658ec..02ee53cc2 100644 --- a/Source/Core/XMLParseTools.h +++ b/Source/Core/XMLParseTools.h @@ -1,72 +1,72 @@ -/* - * This source file is part of RmlUi, the HTML/CSS Interface Middleware - * - * For the latest information, see http://github.com/mikke89/RmlUi - * - * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd - * Copyright (c) 2019 The RmlUi Team, and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#ifndef RMLUI_CORE_XMLPARSETOOLS_H -#define RMLUI_CORE_XMLPARSETOOLS_H - -#include "../../Include/RmlUi/Core/Types.h" - -namespace Rml { - -class Element; - -/** - Tools for aiding in parsing XML documents. - - @author Lloyd Weehuizen - */ - -class XMLParseTools -{ -public: - /// Searchs a string for the specified tag - /// @param tag Tag to find, *must* be in lower case - /// @param string String to search - /// @param closing_tag Is it the closing tag we're looking for? - static const char* FindTag(const char* tag, const char* string, bool closing_tag = false); - /// Reads the next attribute from the string, advancing the pointer - /// @param[in,out] string String to read the attribute from, pointer will be advanced passed the read - /// @param[out] name Name of the attribute read - /// @param[out] value Value of the attribute read - static bool ReadAttribute(const char* &string, String& name, String& value); - - /// Applies the named template to the specified element - /// @param element Element to apply the template to - /// @param template_name Name of the template to apply, in TEMPLATE:ELEMENT_ID form - /// @returns Element to continue the parse from - static Element* ParseTemplate(Element* element, const String& template_name); - - /// Determine the presence of data expression brackets inside XML data. - /// Call this for each iteration through the data string. - /// 'inside_brackets' should be initialized to false. - /// Returns nullptr on success, or an error string on failure. - static const char* ParseDataBrackets(bool& inside_brackets, char c, char previous); -}; - -} // namespace Rml -#endif +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_XMLPARSETOOLS_H +#define RMLUI_CORE_XMLPARSETOOLS_H + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; + +/** + Tools for aiding in parsing XML documents. + + @author Lloyd Weehuizen + */ + +class XMLParseTools +{ +public: + /// Searchs a string for the specified tag + /// @param tag Tag to find, *must* be in lower case + /// @param string String to search + /// @param closing_tag Is it the closing tag we're looking for? + static const char* FindTag(const char* tag, const char* string, bool closing_tag = false); + /// Reads the next attribute from the string, advancing the pointer + /// @param[in,out] string String to read the attribute from, pointer will be advanced passed the read + /// @param[out] name Name of the attribute read + /// @param[out] value Value of the attribute read + static bool ReadAttribute(const char* &string, String& name, String& value); + + /// Applies the named template to the specified element + /// @param element Element to apply the template to + /// @param template_name Name of the template to apply, in TEMPLATE:ELEMENT_ID form + /// @returns Element to continue the parse from + static Element* ParseTemplate(Element* element, const String& template_name); + + /// Determine the presence of data expression brackets inside XML data. + /// Call this for each iteration through the data string. + /// 'inside_brackets' should be initialized to false. + /// Returns nullptr on success, or an error string on failure. + static const char* ParseDataBrackets(bool& inside_brackets, char c, char previous); +}; + +} // namespace Rml +#endif diff --git a/Tests/Source/UnitTests/DataBinding.cpp b/Tests/Source/UnitTests/DataBinding.cpp index 5b242fed3..48d6bf86c 100644 --- a/Tests/Source/UnitTests/DataBinding.cpp +++ b/Tests/Source/UnitTests/DataBinding.cpp @@ -114,6 +114,36 @@ static const String document_rml = R"( )"; +static const String inside_string_rml = R"( + + + Test + + + + + + +
+ +

{{ i0 }}

+

{{ 'i0' }}

+

{{ 'i{}23' }}

+ +
+ +
+)"; struct StringWrap { @@ -393,3 +423,24 @@ TEST_CASE("databinding") TestsShell::ShutdownShell(); } + +TEST_CASE("databinding.inside_string") +{ + Context* context = TestsShell::GetContext(); + REQUIRE(context); + + REQUIRE(InitializeDataBindings(context)); + + ElementDocument* document = context->LoadDocumentFromMemory(inside_string_rml); + REQUIRE(document); + document->Show(); + + context->Update(); + context->Render(); + + TestsShell::RenderLoop(); + + document->Close(); + + TestsShell::ShutdownShell(); +} \ No newline at end of file From 9ba6b7a6b3199a87e7824d9379e75732b76d8b71 Mon Sep 17 00:00:00 2001 From: Dakror Date: Thu, 20 May 2021 14:32:42 +0200 Subject: [PATCH 2/4] fix line endings, thanks windows git --- Source/Core/BaseXMLParser.cpp | 1170 ++++++++++++++++----------------- Source/Core/XMLParseTools.h | 144 ++-- 2 files changed, 657 insertions(+), 657 deletions(-) diff --git a/Source/Core/BaseXMLParser.cpp b/Source/Core/BaseXMLParser.cpp index ad1aacd91..1eaec0e0b 100644 --- a/Source/Core/BaseXMLParser.cpp +++ b/Source/Core/BaseXMLParser.cpp @@ -1,585 +1,585 @@ -/* - * This source file is part of RmlUi, the HTML/CSS Interface Middleware - * - * For the latest information, see http://github.com/mikke89/RmlUi - * - * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd - * Copyright (c) 2019 The RmlUi Team, and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#include "../../Include/RmlUi/Core/BaseXMLParser.h" -#include "../../Include/RmlUi/Core/Profiling.h" -#include "../../Include/RmlUi/Core/Stream.h" -#include "XMLParseTools.h" -#include - -namespace Rml { - -BaseXMLParser::BaseXMLParser() -{} - -BaseXMLParser::~BaseXMLParser() -{} - -// Registers a tag as containing general character data. -void BaseXMLParser::RegisterCDATATag(const String& tag) -{ - if (!tag.empty()) - cdata_tags.insert(StringUtilities::ToLower(tag)); -} - -void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name) -{ - attributes_for_inner_xml_data.insert(attribute_name); -} - -// Parses the given stream as an XML file, and calls the handlers when -// interesting phenomenon are encountered. -void BaseXMLParser::Parse(Stream* stream) -{ - source_url = &stream->GetSourceURL(); - - xml_source.clear(); - - // We read in the whole XML file here. - // TODO: It doesn't look like the Stream interface is used for anything useful. We - // might as well just use a span or StringView, and get completely rid of it. - // @performance Otherwise, use the temporary allocator. - const size_t source_size = stream->Length(); - stream->Read(xml_source, source_size); - - xml_index = 0; - line_number = 1; - line_number_open_tag = 1; - - inner_xml_data = false; - inner_xml_data_terminate_depth = 0; - inner_xml_data_index_begin = 0; - - // Read (er ... skip) the header, if one exists. - ReadHeader(); - // Read the XML body. - ReadBody(); - - xml_source.clear(); - source_url = nullptr; -} - -// Get the current file line number -int BaseXMLParser::GetLineNumber() const -{ - return line_number; -} - -int BaseXMLParser::GetLineNumberOpenTag() const -{ - return line_number_open_tag; -} - -// Called when the parser finds the beginning of an element tag. -void BaseXMLParser::HandleElementStart(const String& RMLUI_UNUSED_PARAMETER(name), const XMLAttributes& RMLUI_UNUSED_PARAMETER(attributes)) -{ - RMLUI_UNUSED(name); - RMLUI_UNUSED(attributes); -} - -// Called when the parser finds the end of an element tag. -void BaseXMLParser::HandleElementEnd(const String& RMLUI_UNUSED_PARAMETER(name)) -{ - RMLUI_UNUSED(name); -} - -// Called when the parser encounters data. -void BaseXMLParser::HandleData(const String& RMLUI_UNUSED_PARAMETER(data), XMLDataType RMLUI_UNUSED_PARAMETER(type)) -{ - RMLUI_UNUSED(data); - RMLUI_UNUSED(type); -} - -/// Returns the source URL of this parse. Only valid during parsing. - -const URL* BaseXMLParser::GetSourceURLPtr() const -{ - return source_url; -} - -void BaseXMLParser::Next() { - xml_index += 1; -} - -bool BaseXMLParser::AtEnd() const { - return xml_index >= xml_source.size(); -} - -char BaseXMLParser::Look() const { - RMLUI_ASSERT(!AtEnd()); - return xml_source[xml_index]; -} - -void BaseXMLParser::HandleElementStartInternal(const String& name, const XMLAttributes& attributes) -{ - line_number_open_tag = line_number; - if (!inner_xml_data) - HandleElementStart(name, attributes); -} - -void BaseXMLParser::HandleElementEndInternal(const String& name) -{ - if (!inner_xml_data) - HandleElementEnd(name); -} - -void BaseXMLParser::HandleDataInternal(const String& data, XMLDataType type) -{ - if (!inner_xml_data) - HandleData(data, type); -} - -void BaseXMLParser::ReadHeader() -{ - if (PeekString("", temp); - } -} - -void BaseXMLParser::ReadBody() -{ - RMLUI_ZoneScoped; - - open_tag_depth = 0; - line_number_open_tag = 0; - - for(;;) - { - // Find the next open tag. - if (!FindString("<", data, true)) - break; - - const size_t xml_index_tag = xml_index - 1; - - // Check what kind of tag this is. - if (PeekString("!--")) - { - // Comment. - String temp; - if (!FindString("-->", temp)) - break; - } - else if (PeekString("![CDATA[")) - { - // CDATA tag; read everything (including markup) until the ending - // CDATA tag. - if (!ReadCDATA()) - break; - } - else if (PeekString("/")) - { - if (!ReadCloseTag(xml_index_tag)) - break; - - // Bail if we've hit the end of the XML data. - if (open_tag_depth == 0) - break; - } - else - { - if (!ReadOpenTag()) - break; - } - } - - // Check for error conditions - if (open_tag_depth > 0) - { - Log::Message(Log::LT_WARNING, "XML parse error on line %d of %s.", GetLineNumber(), source_url->GetURL().c_str()); - } -} - -bool BaseXMLParser::ReadOpenTag() -{ - // Increase the open depth - open_tag_depth++; - - // Opening tag; send data immediately and open the tag. - if (!data.empty()) - { - HandleDataInternal(data, XMLDataType::Text); - data.clear(); - } - - String tag_name; - if (!FindWord(tag_name, "/>")) - return false; - - bool section_opened = false; - - if (PeekString(">")) - { - // Simple open tag. - HandleElementStartInternal(tag_name, XMLAttributes()); - section_opened = true; - } - else if (PeekString("/") && - PeekString(">")) - { - // Empty open tag. - HandleElementStartInternal(tag_name, XMLAttributes()); - HandleElementEndInternal(tag_name); - - // Tag immediately closed, reduce count - open_tag_depth--; - } - else - { - // It appears we have some attributes. Let's parse them. - bool parse_inner_xml_as_data = false; - XMLAttributes attributes; - if (!ReadAttributes(attributes, parse_inner_xml_as_data)) - return false; - - if (PeekString(">")) - { - HandleElementStartInternal(tag_name, attributes); - section_opened = true; - } - else if (PeekString("/") && - PeekString(">")) - { - HandleElementStartInternal(tag_name, attributes); - HandleElementEndInternal(tag_name); - - // Tag immediately closed, reduce count - open_tag_depth--; - } - else - { - return false; - } - - if (section_opened && parse_inner_xml_as_data && !inner_xml_data) - { - inner_xml_data = true; - inner_xml_data_terminate_depth = open_tag_depth; - inner_xml_data_index_begin = xml_index; - } - } - - // Check if this tag needs to be processed as CDATA. - if (section_opened) - { - const String lcase_tag_name = StringUtilities::ToLower(tag_name); - bool is_cdata_tag = (cdata_tags.find(lcase_tag_name) != cdata_tags.end()); - - if (is_cdata_tag) - { - if (ReadCDATA(lcase_tag_name.c_str())) - { - open_tag_depth--; - if (!data.empty()) - { - HandleDataInternal(data, XMLDataType::CData); - data.clear(); - } - HandleElementEndInternal(tag_name); - - return true; - } - - return false; - } - } - - return true; -} - -bool BaseXMLParser::ReadCloseTag(const size_t xml_index_tag) -{ - if (inner_xml_data && open_tag_depth == inner_xml_data_terminate_depth) - { - // Closing the tag that initiated the inner xml data parsing. Set all its contents as Data to be - // submitted next, and disable the mode to resume normal parsing behavior. - RMLUI_ASSERT(inner_xml_data_index_begin <= xml_index_tag); - inner_xml_data = false; - data = xml_source.substr(inner_xml_data_index_begin, xml_index_tag - inner_xml_data_index_begin); - HandleDataInternal(data, XMLDataType::InnerXML); - data.clear(); - } - - // Closing tag; send data immediately and close the tag. - if (!data.empty()) - { - HandleDataInternal(data, XMLDataType::Text); - data.clear(); - } - - String tag_name; - if (!FindString(">", tag_name)) - return false; - - HandleElementEndInternal(StringUtilities::StripWhitespace(tag_name)); - - - // Tag closed, reduce count - open_tag_depth--; - - - return true; -} - -bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes, bool& parse_raw_xml_content) -{ - for (;;) - { - String attribute; - String value; - - // Get the attribute name - if (!FindWord(attribute, "=/>")) - { - return false; - } - - // Check if theres an assigned value - if (PeekString("=")) - { - if (PeekString("\"")) - { - if (!FindString("\"", value)) - return false; - } - else if (PeekString("'")) - { - if (!FindString("'", value)) - return false; - } - else if (!FindWord(value, "/>")) - { - return false; - } - } - - if (attributes_for_inner_xml_data.count(attribute) == 1) - parse_raw_xml_content = true; - - attributes[attribute] = StringUtilities::DecodeRml(value); - - // Check for the end of the tag. - if (PeekString("/", false) || PeekString(">", false)) - return true; - } -} - -bool BaseXMLParser::ReadCDATA(const char* tag_terminator) -{ - String cdata; - if (tag_terminator == nullptr) - { - FindString("]]>", cdata); - data += cdata; - return true; - } - else - { - for (;;) - { - // Search for the next tag opening. - if (!FindString("<", cdata)) - return false; - - if (PeekString("/", false)) - { - String tag; - if (FindString(">", tag)) - { - size_t slash_pos = tag.find('/'); - String tag_name = StringUtilities::StripWhitespace(slash_pos == String::npos ? tag : tag.substr(slash_pos + 1)); - if (StringUtilities::ToLower(tag_name) == tag_terminator) - { - data += cdata; - return true; - } - else - { - cdata += '<' + tag + '>'; - } - } - else - cdata += "<"; - } - else - cdata += "<"; - } - } -} - -// Reads from the stream until a complete word is found. -bool BaseXMLParser::FindWord(String& word, const char* terminators) -{ - while (!AtEnd()) - { - char c = Look(); - - // Count line numbers - if (c == '\n') - { - line_number++; - } - - // Ignore white space - if (StringUtilities::IsWhitespace(c)) - { - if (word.empty()) - { - Next(); - continue; - } - else - return true; - } - - // Check for termination condition - if (terminators && strchr(terminators, c)) - { - return !word.empty(); - } - - word += c; - Next(); - } - - return false; -} - -// Reads from the stream until the given character set is found. -bool BaseXMLParser::FindString(const char* string, String& data, bool escape_brackets) -{ - int index = 0; - bool in_brackets = false; - bool in_string = false; - char previous = 0; - - while (string[index]) - { - if (AtEnd()) - return false; - - const char c = Look(); - - // Count line numbers - if (c == '\n') - { - line_number++; - } - - if(escape_brackets) - { - if (in_brackets && c == '\'') { - in_string = !in_string; - } - - if (!in_string) { - const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, c, previous); - if (error_str) - { - Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str); - return false; - } - } - } - - if (c == string[index] && !in_brackets) - { - index += 1; - } - else - { - if (index > 0) - { - data += String(string, index); - index = 0; - } - - data += c; - } - - previous = c; - Next(); - } - - return true; -} - -// Returns true if the next sequence of characters in the stream matches the -// given string. -bool BaseXMLParser::PeekString(const char* string, bool consume) -{ - const size_t start_index = xml_index; - const int start_line = line_number; - bool success = true; - int i = 0; - while (string[i]) - { - if (AtEnd()) - { - success = false; - break; - } - - const char c = Look(); - - // Count line numbers - if (c == '\n') - { - line_number++; - } - - // Seek past all the whitespace if we haven't hit the initial character yet. - if (i == 0 && StringUtilities::IsWhitespace(c)) - { - Next(); - } - else - { - if (c != string[i]) - { - success = false; - break; - } - - i++; - Next(); - } - } - - // Set the index to the start index unless we are consuming. - if (!consume || !success) - { - xml_index = start_index; - line_number = start_line; - } - - return success; -} - -} // namespace Rml +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "../../Include/RmlUi/Core/BaseXMLParser.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/Stream.h" +#include "XMLParseTools.h" +#include + +namespace Rml { + +BaseXMLParser::BaseXMLParser() +{} + +BaseXMLParser::~BaseXMLParser() +{} + +// Registers a tag as containing general character data. +void BaseXMLParser::RegisterCDATATag(const String& tag) +{ + if (!tag.empty()) + cdata_tags.insert(StringUtilities::ToLower(tag)); +} + +void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name) +{ + attributes_for_inner_xml_data.insert(attribute_name); +} + +// Parses the given stream as an XML file, and calls the handlers when +// interesting phenomenon are encountered. +void BaseXMLParser::Parse(Stream* stream) +{ + source_url = &stream->GetSourceURL(); + + xml_source.clear(); + + // We read in the whole XML file here. + // TODO: It doesn't look like the Stream interface is used for anything useful. We + // might as well just use a span or StringView, and get completely rid of it. + // @performance Otherwise, use the temporary allocator. + const size_t source_size = stream->Length(); + stream->Read(xml_source, source_size); + + xml_index = 0; + line_number = 1; + line_number_open_tag = 1; + + inner_xml_data = false; + inner_xml_data_terminate_depth = 0; + inner_xml_data_index_begin = 0; + + // Read (er ... skip) the header, if one exists. + ReadHeader(); + // Read the XML body. + ReadBody(); + + xml_source.clear(); + source_url = nullptr; +} + +// Get the current file line number +int BaseXMLParser::GetLineNumber() const +{ + return line_number; +} + +int BaseXMLParser::GetLineNumberOpenTag() const +{ + return line_number_open_tag; +} + +// Called when the parser finds the beginning of an element tag. +void BaseXMLParser::HandleElementStart(const String& RMLUI_UNUSED_PARAMETER(name), const XMLAttributes& RMLUI_UNUSED_PARAMETER(attributes)) +{ + RMLUI_UNUSED(name); + RMLUI_UNUSED(attributes); +} + +// Called when the parser finds the end of an element tag. +void BaseXMLParser::HandleElementEnd(const String& RMLUI_UNUSED_PARAMETER(name)) +{ + RMLUI_UNUSED(name); +} + +// Called when the parser encounters data. +void BaseXMLParser::HandleData(const String& RMLUI_UNUSED_PARAMETER(data), XMLDataType RMLUI_UNUSED_PARAMETER(type)) +{ + RMLUI_UNUSED(data); + RMLUI_UNUSED(type); +} + +/// Returns the source URL of this parse. Only valid during parsing. + +const URL* BaseXMLParser::GetSourceURLPtr() const +{ + return source_url; +} + +void BaseXMLParser::Next() { + xml_index += 1; +} + +bool BaseXMLParser::AtEnd() const { + return xml_index >= xml_source.size(); +} + +char BaseXMLParser::Look() const { + RMLUI_ASSERT(!AtEnd()); + return xml_source[xml_index]; +} + +void BaseXMLParser::HandleElementStartInternal(const String& name, const XMLAttributes& attributes) +{ + line_number_open_tag = line_number; + if (!inner_xml_data) + HandleElementStart(name, attributes); +} + +void BaseXMLParser::HandleElementEndInternal(const String& name) +{ + if (!inner_xml_data) + HandleElementEnd(name); +} + +void BaseXMLParser::HandleDataInternal(const String& data, XMLDataType type) +{ + if (!inner_xml_data) + HandleData(data, type); +} + +void BaseXMLParser::ReadHeader() +{ + if (PeekString("", temp); + } +} + +void BaseXMLParser::ReadBody() +{ + RMLUI_ZoneScoped; + + open_tag_depth = 0; + line_number_open_tag = 0; + + for(;;) + { + // Find the next open tag. + if (!FindString("<", data, true)) + break; + + const size_t xml_index_tag = xml_index - 1; + + // Check what kind of tag this is. + if (PeekString("!--")) + { + // Comment. + String temp; + if (!FindString("-->", temp)) + break; + } + else if (PeekString("![CDATA[")) + { + // CDATA tag; read everything (including markup) until the ending + // CDATA tag. + if (!ReadCDATA()) + break; + } + else if (PeekString("/")) + { + if (!ReadCloseTag(xml_index_tag)) + break; + + // Bail if we've hit the end of the XML data. + if (open_tag_depth == 0) + break; + } + else + { + if (!ReadOpenTag()) + break; + } + } + + // Check for error conditions + if (open_tag_depth > 0) + { + Log::Message(Log::LT_WARNING, "XML parse error on line %d of %s.", GetLineNumber(), source_url->GetURL().c_str()); + } +} + +bool BaseXMLParser::ReadOpenTag() +{ + // Increase the open depth + open_tag_depth++; + + // Opening tag; send data immediately and open the tag. + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::Text); + data.clear(); + } + + String tag_name; + if (!FindWord(tag_name, "/>")) + return false; + + bool section_opened = false; + + if (PeekString(">")) + { + // Simple open tag. + HandleElementStartInternal(tag_name, XMLAttributes()); + section_opened = true; + } + else if (PeekString("/") && + PeekString(">")) + { + // Empty open tag. + HandleElementStartInternal(tag_name, XMLAttributes()); + HandleElementEndInternal(tag_name); + + // Tag immediately closed, reduce count + open_tag_depth--; + } + else + { + // It appears we have some attributes. Let's parse them. + bool parse_inner_xml_as_data = false; + XMLAttributes attributes; + if (!ReadAttributes(attributes, parse_inner_xml_as_data)) + return false; + + if (PeekString(">")) + { + HandleElementStartInternal(tag_name, attributes); + section_opened = true; + } + else if (PeekString("/") && + PeekString(">")) + { + HandleElementStartInternal(tag_name, attributes); + HandleElementEndInternal(tag_name); + + // Tag immediately closed, reduce count + open_tag_depth--; + } + else + { + return false; + } + + if (section_opened && parse_inner_xml_as_data && !inner_xml_data) + { + inner_xml_data = true; + inner_xml_data_terminate_depth = open_tag_depth; + inner_xml_data_index_begin = xml_index; + } + } + + // Check if this tag needs to be processed as CDATA. + if (section_opened) + { + const String lcase_tag_name = StringUtilities::ToLower(tag_name); + bool is_cdata_tag = (cdata_tags.find(lcase_tag_name) != cdata_tags.end()); + + if (is_cdata_tag) + { + if (ReadCDATA(lcase_tag_name.c_str())) + { + open_tag_depth--; + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::CData); + data.clear(); + } + HandleElementEndInternal(tag_name); + + return true; + } + + return false; + } + } + + return true; +} + +bool BaseXMLParser::ReadCloseTag(const size_t xml_index_tag) +{ + if (inner_xml_data && open_tag_depth == inner_xml_data_terminate_depth) + { + // Closing the tag that initiated the inner xml data parsing. Set all its contents as Data to be + // submitted next, and disable the mode to resume normal parsing behavior. + RMLUI_ASSERT(inner_xml_data_index_begin <= xml_index_tag); + inner_xml_data = false; + data = xml_source.substr(inner_xml_data_index_begin, xml_index_tag - inner_xml_data_index_begin); + HandleDataInternal(data, XMLDataType::InnerXML); + data.clear(); + } + + // Closing tag; send data immediately and close the tag. + if (!data.empty()) + { + HandleDataInternal(data, XMLDataType::Text); + data.clear(); + } + + String tag_name; + if (!FindString(">", tag_name)) + return false; + + HandleElementEndInternal(StringUtilities::StripWhitespace(tag_name)); + + + // Tag closed, reduce count + open_tag_depth--; + + + return true; +} + +bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes, bool& parse_raw_xml_content) +{ + for (;;) + { + String attribute; + String value; + + // Get the attribute name + if (!FindWord(attribute, "=/>")) + { + return false; + } + + // Check if theres an assigned value + if (PeekString("=")) + { + if (PeekString("\"")) + { + if (!FindString("\"", value)) + return false; + } + else if (PeekString("'")) + { + if (!FindString("'", value)) + return false; + } + else if (!FindWord(value, "/>")) + { + return false; + } + } + + if (attributes_for_inner_xml_data.count(attribute) == 1) + parse_raw_xml_content = true; + + attributes[attribute] = StringUtilities::DecodeRml(value); + + // Check for the end of the tag. + if (PeekString("/", false) || PeekString(">", false)) + return true; + } +} + +bool BaseXMLParser::ReadCDATA(const char* tag_terminator) +{ + String cdata; + if (tag_terminator == nullptr) + { + FindString("]]>", cdata); + data += cdata; + return true; + } + else + { + for (;;) + { + // Search for the next tag opening. + if (!FindString("<", cdata)) + return false; + + if (PeekString("/", false)) + { + String tag; + if (FindString(">", tag)) + { + size_t slash_pos = tag.find('/'); + String tag_name = StringUtilities::StripWhitespace(slash_pos == String::npos ? tag : tag.substr(slash_pos + 1)); + if (StringUtilities::ToLower(tag_name) == tag_terminator) + { + data += cdata; + return true; + } + else + { + cdata += '<' + tag + '>'; + } + } + else + cdata += "<"; + } + else + cdata += "<"; + } + } +} + +// Reads from the stream until a complete word is found. +bool BaseXMLParser::FindWord(String& word, const char* terminators) +{ + while (!AtEnd()) + { + char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + // Ignore white space + if (StringUtilities::IsWhitespace(c)) + { + if (word.empty()) + { + Next(); + continue; + } + else + return true; + } + + // Check for termination condition + if (terminators && strchr(terminators, c)) + { + return !word.empty(); + } + + word += c; + Next(); + } + + return false; +} + +// Reads from the stream until the given character set is found. +bool BaseXMLParser::FindString(const char* string, String& data, bool escape_brackets) +{ + int index = 0; + bool in_brackets = false; + bool in_string = false; + char previous = 0; + + while (string[index]) + { + if (AtEnd()) + return false; + + const char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + if(escape_brackets) + { + if (in_brackets && c == '\'') { + in_string = !in_string; + } + + if (!in_string) { + const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str); + return false; + } + } + } + + if (c == string[index] && !in_brackets) + { + index += 1; + } + else + { + if (index > 0) + { + data += String(string, index); + index = 0; + } + + data += c; + } + + previous = c; + Next(); + } + + return true; +} + +// Returns true if the next sequence of characters in the stream matches the +// given string. +bool BaseXMLParser::PeekString(const char* string, bool consume) +{ + const size_t start_index = xml_index; + const int start_line = line_number; + bool success = true; + int i = 0; + while (string[i]) + { + if (AtEnd()) + { + success = false; + break; + } + + const char c = Look(); + + // Count line numbers + if (c == '\n') + { + line_number++; + } + + // Seek past all the whitespace if we haven't hit the initial character yet. + if (i == 0 && StringUtilities::IsWhitespace(c)) + { + Next(); + } + else + { + if (c != string[i]) + { + success = false; + break; + } + + i++; + Next(); + } + } + + // Set the index to the start index unless we are consuming. + if (!consume || !success) + { + xml_index = start_index; + line_number = start_line; + } + + return success; +} + +} // namespace Rml diff --git a/Source/Core/XMLParseTools.h b/Source/Core/XMLParseTools.h index 02ee53cc2..1887658ec 100644 --- a/Source/Core/XMLParseTools.h +++ b/Source/Core/XMLParseTools.h @@ -1,72 +1,72 @@ -/* - * This source file is part of RmlUi, the HTML/CSS Interface Middleware - * - * For the latest information, see http://github.com/mikke89/RmlUi - * - * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd - * Copyright (c) 2019 The RmlUi Team, and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#ifndef RMLUI_CORE_XMLPARSETOOLS_H -#define RMLUI_CORE_XMLPARSETOOLS_H - -#include "../../Include/RmlUi/Core/Types.h" - -namespace Rml { - -class Element; - -/** - Tools for aiding in parsing XML documents. - - @author Lloyd Weehuizen - */ - -class XMLParseTools -{ -public: - /// Searchs a string for the specified tag - /// @param tag Tag to find, *must* be in lower case - /// @param string String to search - /// @param closing_tag Is it the closing tag we're looking for? - static const char* FindTag(const char* tag, const char* string, bool closing_tag = false); - /// Reads the next attribute from the string, advancing the pointer - /// @param[in,out] string String to read the attribute from, pointer will be advanced passed the read - /// @param[out] name Name of the attribute read - /// @param[out] value Value of the attribute read - static bool ReadAttribute(const char* &string, String& name, String& value); - - /// Applies the named template to the specified element - /// @param element Element to apply the template to - /// @param template_name Name of the template to apply, in TEMPLATE:ELEMENT_ID form - /// @returns Element to continue the parse from - static Element* ParseTemplate(Element* element, const String& template_name); - - /// Determine the presence of data expression brackets inside XML data. - /// Call this for each iteration through the data string. - /// 'inside_brackets' should be initialized to false. - /// Returns nullptr on success, or an error string on failure. - static const char* ParseDataBrackets(bool& inside_brackets, char c, char previous); -}; - -} // namespace Rml -#endif +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_XMLPARSETOOLS_H +#define RMLUI_CORE_XMLPARSETOOLS_H + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Element; + +/** + Tools for aiding in parsing XML documents. + + @author Lloyd Weehuizen + */ + +class XMLParseTools +{ +public: + /// Searchs a string for the specified tag + /// @param tag Tag to find, *must* be in lower case + /// @param string String to search + /// @param closing_tag Is it the closing tag we're looking for? + static const char* FindTag(const char* tag, const char* string, bool closing_tag = false); + /// Reads the next attribute from the string, advancing the pointer + /// @param[in,out] string String to read the attribute from, pointer will be advanced passed the read + /// @param[out] name Name of the attribute read + /// @param[out] value Value of the attribute read + static bool ReadAttribute(const char* &string, String& name, String& value); + + /// Applies the named template to the specified element + /// @param element Element to apply the template to + /// @param template_name Name of the template to apply, in TEMPLATE:ELEMENT_ID form + /// @returns Element to continue the parse from + static Element* ParseTemplate(Element* element, const String& template_name); + + /// Determine the presence of data expression brackets inside XML data. + /// Call this for each iteration through the data string. + /// 'inside_brackets' should be initialized to false. + /// Returns nullptr on success, or an error string on failure. + static const char* ParseDataBrackets(bool& inside_brackets, char c, char previous); +}; + +} // namespace Rml +#endif From 07624ddaf107d6b824205a5a17e2855cf0ef72f5 Mon Sep 17 00:00:00 2001 From: Dakror Date: Fri, 21 May 2021 18:16:58 +0200 Subject: [PATCH 3/4] use string logic inside dataviewtext initialize --- Source/Core/BaseXMLParser.cpp | 16 +- Source/Core/DataViewDefault.cpp | 56 ++-- Source/Core/Factory.cpp | 16 +- Source/Core/XMLParseTools.cpp | 386 +++++++++++++------------ Source/Core/XMLParseTools.h | 3 +- Tests/Source/UnitTests/DataBinding.cpp | 5 + 6 files changed, 252 insertions(+), 230 deletions(-) diff --git a/Source/Core/BaseXMLParser.cpp b/Source/Core/BaseXMLParser.cpp index 1eaec0e0b..21b00d25e 100644 --- a/Source/Core/BaseXMLParser.cpp +++ b/Source/Core/BaseXMLParser.cpp @@ -494,17 +494,11 @@ bool BaseXMLParser::FindString(const char* string, String& data, bool escape_bra if(escape_brackets) { - if (in_brackets && c == '\'') { - in_string = !in_string; - } - - if (!in_string) { - const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, c, previous); - if (error_str) - { - Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str); - return false; - } + const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, in_string, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str); + return false; } } diff --git a/Source/Core/DataViewDefault.cpp b/Source/Core/DataViewDefault.cpp index 5a86e707e..70599317a 100644 --- a/Source/Core/DataViewDefault.cpp +++ b/Source/Core/DataViewDefault.cpp @@ -29,6 +29,7 @@ #include "DataViewDefault.h" #include "DataExpression.h" #include "DataModel.h" +#include "XMLParseTools.h" #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/DataVariable.h" #include "../../Include/RmlUi/Core/Element.h" @@ -328,35 +329,56 @@ bool DataViewText::Initialize(DataModel& model, Element* element, const String& DataExpressionInterface expression_interface(&model, element); - size_t previous_close_brackets = 0; size_t begin_brackets = 0; - while ((begin_brackets = in_text.find("{{", begin_brackets)) != String::npos) - { - text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.begin() + begin_brackets); + size_t cur = 0; + char previous = 0; + bool was_in_brackets = false; + bool in_brackets = false; + bool in_string = false; - const size_t begin_name = begin_brackets + 2; - const size_t end_name = in_text.find("}}", begin_name); + for(char c : in_text) { + was_in_brackets = in_brackets; - if (end_name == String::npos) + const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, in_string, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "Failed to parse data view text '%s'. %s", in_text.c_str(), error_str); return false; + } + + if (!was_in_brackets && in_brackets) + { + begin_brackets = cur; + } + else if (was_in_brackets && !in_brackets) + { + DataEntry entry; + entry.index = text.size(); + entry.data_expression = MakeUnique(String(in_text.begin() + begin_brackets + 1, in_text.begin() + cur - 2)); - DataEntry entry; - entry.index = text.size(); - entry.data_expression = MakeUnique(String(in_text.begin() + begin_name, in_text.begin() + end_name)); + if (entry.data_expression->Parse(expression_interface, false)) + data_entries.push_back(std::move(entry)); - if (entry.data_expression->Parse(expression_interface, false)) - data_entries.push_back(std::move(entry)); + // Reset char so that it won't appended to the output + c = 0; + } + else if (!in_brackets && previous) + { + text.push_back(previous); + } - previous_close_brackets = end_name + 2; - begin_brackets = previous_close_brackets; + cur++; + previous = c; + } + + if (!in_brackets && previous) + { + text.push_back(previous); } if (data_entries.empty()) return false; - if (previous_close_brackets < in_text.size()) - text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.end()); - return true; } diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 2bafc1878..bb2e437d7 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -404,17 +404,11 @@ bool Factory::InstanceElementText(Element* parent, const String& in_text) char previous = 0; for (const char c : text) { - if (inside_brackets && c == '\'') { - inside_string = !inside_string; - } - - if (!inside_string) { - const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, c, previous); - if (error_str) - { - Log::Message(Log::LT_WARNING, "Failed to instance text element '%s'. %s", text.c_str(), error_str); - return false; - } + const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, inside_string, c, previous); + if (error_str) + { + Log::Message(Log::LT_WARNING, "Failed to instance text element '%s'. %s", text.c_str(), error_str); + return false; } if (inside_brackets) diff --git a/Source/Core/XMLParseTools.cpp b/Source/Core/XMLParseTools.cpp index d47189854..c1c491528 100644 --- a/Source/Core/XMLParseTools.cpp +++ b/Source/Core/XMLParseTools.cpp @@ -1,190 +1,196 @@ -/* - * This source file is part of RmlUi, the HTML/CSS Interface Middleware - * - * For the latest information, see http://github.com/mikke89/RmlUi - * - * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd - * Copyright (c) 2019 The RmlUi Team, and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#include "XMLParseTools.h" -#include "../../Include/RmlUi/Core/StreamMemory.h" -#include "../../Include/RmlUi/Core/ElementDocument.h" -#include "../../Include/RmlUi/Core/StringUtilities.h" -#include "../../Include/RmlUi/Core/Types.h" -#include "TemplateCache.h" -#include "Template.h" -#include -#include - -namespace Rml { - -// Searchs a string for the specified tag -// NOTE: tag *MUST* be in lowercase -const char* XMLParseTools::FindTag(const char* tag, const char* string, bool closing_tag) -{ - const size_t length = strlen(tag); - const char* ptr = string; - bool found_closing = false; - - while (*ptr) - { - // Check if the first character matches - if (tolower((*ptr)) == tag[0]) - { - // If it does, check the whole word - if (StringUtilities::StringCompareCaseInsensitive(StringView(ptr, ptr + length), StringView(tag, tag + length))) - { - // Check for opening <, loop back in the string skipping white space and forward slashes if - // we're looking for the closing tag - const char* tag_start = ptr - 1; - while (tag_start > string && (StringUtilities::IsWhitespace(*tag_start) || *tag_start == '/')) - { - if (*tag_start == '/') - found_closing = true; - tag_start--; - } - - // If the character we're looking at is a <, and found closing matches closing tag, - // its the tag we're looking for - if (*tag_start == '<' && found_closing == closing_tag) - return tag_start; - - // Otherwise, keep looking - } - } - ptr++; - } - - return nullptr; -} - -bool XMLParseTools::ReadAttribute(const char* &string, String& name, String& value) -{ - const char* ptr = string; - - name = ""; - value = ""; - - // Skip whitespace - while (StringUtilities::IsWhitespace(*ptr)) - ptr++; - - // Look for the end of the attribute name - bool found_whitespace = false; - while (*ptr != '=' && *ptr != '>' && (!found_whitespace || StringUtilities::IsWhitespace(*ptr))) - { - if (StringUtilities::IsWhitespace(*ptr)) - found_whitespace = true; - else - name += *ptr; - ptr++; - } - if (*ptr == '>') - return false; - - // If we stopped on an equals, parse the value - if (*ptr == '=') - { - - // Skip over white space, ='s and quotes - bool quoted = false; - while (StringUtilities::IsWhitespace(*ptr) || *ptr == '\'' || *ptr == '"' || *ptr == '=') - { - if (*ptr == '\'' || *ptr == '"') - quoted = true; - ptr++; - } - if (*ptr == '>') - return false; - - // Store the value - while (*ptr != '\'' && *ptr != '"' && *ptr != '>' && (*ptr != ' ' || quoted)) - { - value += *ptr++; - } - if (*ptr == '>') - return false; - - // Advance passed the quote - if (quoted) - ptr++; - } - else - { - ptr--; - } - - // Update the string pointer - string = ptr; - - return true; -} - -Element* XMLParseTools::ParseTemplate(Element* element, const String& template_name) -{ - // Load the template, and parse it - Template* parse_template = TemplateCache::GetTemplate(template_name); - if (!parse_template) - { - Log::ParseError(element->GetOwnerDocument()->GetSourceURL(), -1, "Failed to find template '%s'.", template_name.c_str()); - return element; - } - - return parse_template->ParseTemplate(element); -} - -const char* XMLParseTools::ParseDataBrackets(bool& inside_brackets, char c, char previous) -{ - if (inside_brackets) - { - if (c == '}' && previous == '}') - inside_brackets = false; - - else if (c == '{' && previous == '{') - return "Nested double curly brackets are illegal."; - - else if (previous == '}' && c != '}') - return "Single closing curly bracket encountered, use double curly brackets to close an expression."; - - else if (previous == '/' && c == '>') - return "Closing double curly brackets not found, XML end node encountered first."; - - else if (previous == '<' && c == '/') - return "Closing double curly brackets not found, XML end node encountered first."; - } - else - { - if (c == '{' && previous == '{') - { - inside_brackets = true; - } - else if (c == '}' && previous == '}') - { - return "Closing double curly brackets encountered outside an expression."; - } - } - - return nullptr; -} - -} // namespace Rml +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "XMLParseTools.h" +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "TemplateCache.h" +#include "Template.h" +#include +#include + +namespace Rml { + +// Searchs a string for the specified tag +// NOTE: tag *MUST* be in lowercase +const char* XMLParseTools::FindTag(const char* tag, const char* string, bool closing_tag) +{ + const size_t length = strlen(tag); + const char* ptr = string; + bool found_closing = false; + + while (*ptr) + { + // Check if the first character matches + if (tolower((*ptr)) == tag[0]) + { + // If it does, check the whole word + if (StringUtilities::StringCompareCaseInsensitive(StringView(ptr, ptr + length), StringView(tag, tag + length))) + { + // Check for opening <, loop back in the string skipping white space and forward slashes if + // we're looking for the closing tag + const char* tag_start = ptr - 1; + while (tag_start > string && (StringUtilities::IsWhitespace(*tag_start) || *tag_start == '/')) + { + if (*tag_start == '/') + found_closing = true; + tag_start--; + } + + // If the character we're looking at is a <, and found closing matches closing tag, + // its the tag we're looking for + if (*tag_start == '<' && found_closing == closing_tag) + return tag_start; + + // Otherwise, keep looking + } + } + ptr++; + } + + return nullptr; +} + +bool XMLParseTools::ReadAttribute(const char* &string, String& name, String& value) +{ + const char* ptr = string; + + name = ""; + value = ""; + + // Skip whitespace + while (StringUtilities::IsWhitespace(*ptr)) + ptr++; + + // Look for the end of the attribute name + bool found_whitespace = false; + while (*ptr != '=' && *ptr != '>' && (!found_whitespace || StringUtilities::IsWhitespace(*ptr))) + { + if (StringUtilities::IsWhitespace(*ptr)) + found_whitespace = true; + else + name += *ptr; + ptr++; + } + if (*ptr == '>') + return false; + + // If we stopped on an equals, parse the value + if (*ptr == '=') + { + + // Skip over white space, ='s and quotes + bool quoted = false; + while (StringUtilities::IsWhitespace(*ptr) || *ptr == '\'' || *ptr == '"' || *ptr == '=') + { + if (*ptr == '\'' || *ptr == '"') + quoted = true; + ptr++; + } + if (*ptr == '>') + return false; + + // Store the value + while (*ptr != '\'' && *ptr != '"' && *ptr != '>' && (*ptr != ' ' || quoted)) + { + value += *ptr++; + } + if (*ptr == '>') + return false; + + // Advance passed the quote + if (quoted) + ptr++; + } + else + { + ptr--; + } + + // Update the string pointer + string = ptr; + + return true; +} + +Element* XMLParseTools::ParseTemplate(Element* element, const String& template_name) +{ + // Load the template, and parse it + Template* parse_template = TemplateCache::GetTemplate(template_name); + if (!parse_template) + { + Log::ParseError(element->GetOwnerDocument()->GetSourceURL(), -1, "Failed to find template '%s'.", template_name.c_str()); + return element; + } + + return parse_template->ParseTemplate(element); +} + +const char* XMLParseTools::ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous) +{ + if (inside_brackets) + { + if (c == '\'') + inside_string = !inside_string; + + if(!inside_string) + { + if (c == '}' && previous == '}') + inside_brackets = false; + + else if (c == '{' && previous == '{') + return "Nested double curly brackets are illegal."; + + else if (previous == '}' && c != '}') + return "Single closing curly bracket encountered, use double curly brackets to close an expression."; + + else if (previous == '/' && c == '>') + return "Closing double curly brackets not found, XML end node encountered first."; + + else if (previous == '<' && c == '/') + return "Closing double curly brackets not found, XML end node encountered first."; + } + } + else + { + if (c == '{' && previous == '{') + { + inside_brackets = true; + } + else if (c == '}' && previous == '}') + { + return "Closing double curly brackets encountered outside an expression."; + } + } + + return nullptr; +} + +} // namespace Rml diff --git a/Source/Core/XMLParseTools.h b/Source/Core/XMLParseTools.h index 1887658ec..747fcbc14 100644 --- a/Source/Core/XMLParseTools.h +++ b/Source/Core/XMLParseTools.h @@ -64,8 +64,9 @@ class XMLParseTools /// Determine the presence of data expression brackets inside XML data. /// Call this for each iteration through the data string. /// 'inside_brackets' should be initialized to false. + /// 'inside_string' should be initialized to false. /// Returns nullptr on success, or an error string on failure. - static const char* ParseDataBrackets(bool& inside_brackets, char c, char previous); + static const char* ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous); }; } // namespace Rml diff --git a/Tests/Source/UnitTests/DataBinding.cpp b/Tests/Source/UnitTests/DataBinding.cpp index 48d6bf86c..cd7824229 100644 --- a/Tests/Source/UnitTests/DataBinding.cpp +++ b/Tests/Source/UnitTests/DataBinding.cpp @@ -139,6 +139,8 @@ static const String inside_string_rml = R"(

{{ i0 }}

{{ 'i0' }}

{{ 'i{}23' }}

+

before {{ 'i{{test}}23' }} test

+

a {{ 'i' }} b {{ 'j' }} c

@@ -440,6 +442,9 @@ TEST_CASE("databinding.inside_string") TestsShell::RenderLoop(); + CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "before i{{test}}23 test"); + CHECK(document->QuerySelector("p:nth-child(5)")->GetInnerRML() == "a i b j c"); + document->Close(); TestsShell::ShutdownShell(); From 55b26045a12faa71b3262ca2aa41dd2799d5a0d2 Mon Sep 17 00:00:00 2001 From: Dakror Date: Fri, 21 May 2021 20:33:56 +0200 Subject: [PATCH 4/4] fix line endings --- Source/Core/XMLParseTools.cpp | 392 +++++++++++++++++----------------- 1 file changed, 196 insertions(+), 196 deletions(-) diff --git a/Source/Core/XMLParseTools.cpp b/Source/Core/XMLParseTools.cpp index c1c491528..58f0ace79 100644 --- a/Source/Core/XMLParseTools.cpp +++ b/Source/Core/XMLParseTools.cpp @@ -1,196 +1,196 @@ -/* - * This source file is part of RmlUi, the HTML/CSS Interface Middleware - * - * For the latest information, see http://github.com/mikke89/RmlUi - * - * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd - * Copyright (c) 2019 The RmlUi Team, and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#include "XMLParseTools.h" -#include "../../Include/RmlUi/Core/StreamMemory.h" -#include "../../Include/RmlUi/Core/ElementDocument.h" -#include "../../Include/RmlUi/Core/StringUtilities.h" -#include "../../Include/RmlUi/Core/Types.h" -#include "TemplateCache.h" -#include "Template.h" -#include -#include - -namespace Rml { - -// Searchs a string for the specified tag -// NOTE: tag *MUST* be in lowercase -const char* XMLParseTools::FindTag(const char* tag, const char* string, bool closing_tag) -{ - const size_t length = strlen(tag); - const char* ptr = string; - bool found_closing = false; - - while (*ptr) - { - // Check if the first character matches - if (tolower((*ptr)) == tag[0]) - { - // If it does, check the whole word - if (StringUtilities::StringCompareCaseInsensitive(StringView(ptr, ptr + length), StringView(tag, tag + length))) - { - // Check for opening <, loop back in the string skipping white space and forward slashes if - // we're looking for the closing tag - const char* tag_start = ptr - 1; - while (tag_start > string && (StringUtilities::IsWhitespace(*tag_start) || *tag_start == '/')) - { - if (*tag_start == '/') - found_closing = true; - tag_start--; - } - - // If the character we're looking at is a <, and found closing matches closing tag, - // its the tag we're looking for - if (*tag_start == '<' && found_closing == closing_tag) - return tag_start; - - // Otherwise, keep looking - } - } - ptr++; - } - - return nullptr; -} - -bool XMLParseTools::ReadAttribute(const char* &string, String& name, String& value) -{ - const char* ptr = string; - - name = ""; - value = ""; - - // Skip whitespace - while (StringUtilities::IsWhitespace(*ptr)) - ptr++; - - // Look for the end of the attribute name - bool found_whitespace = false; - while (*ptr != '=' && *ptr != '>' && (!found_whitespace || StringUtilities::IsWhitespace(*ptr))) - { - if (StringUtilities::IsWhitespace(*ptr)) - found_whitespace = true; - else - name += *ptr; - ptr++; - } - if (*ptr == '>') - return false; - - // If we stopped on an equals, parse the value - if (*ptr == '=') - { - - // Skip over white space, ='s and quotes - bool quoted = false; - while (StringUtilities::IsWhitespace(*ptr) || *ptr == '\'' || *ptr == '"' || *ptr == '=') - { - if (*ptr == '\'' || *ptr == '"') - quoted = true; - ptr++; - } - if (*ptr == '>') - return false; - - // Store the value - while (*ptr != '\'' && *ptr != '"' && *ptr != '>' && (*ptr != ' ' || quoted)) - { - value += *ptr++; - } - if (*ptr == '>') - return false; - - // Advance passed the quote - if (quoted) - ptr++; - } - else - { - ptr--; - } - - // Update the string pointer - string = ptr; - - return true; -} - -Element* XMLParseTools::ParseTemplate(Element* element, const String& template_name) -{ - // Load the template, and parse it - Template* parse_template = TemplateCache::GetTemplate(template_name); - if (!parse_template) - { - Log::ParseError(element->GetOwnerDocument()->GetSourceURL(), -1, "Failed to find template '%s'.", template_name.c_str()); - return element; - } - - return parse_template->ParseTemplate(element); -} - -const char* XMLParseTools::ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous) -{ - if (inside_brackets) - { - if (c == '\'') - inside_string = !inside_string; - - if(!inside_string) - { - if (c == '}' && previous == '}') - inside_brackets = false; - - else if (c == '{' && previous == '{') - return "Nested double curly brackets are illegal."; - - else if (previous == '}' && c != '}') - return "Single closing curly bracket encountered, use double curly brackets to close an expression."; - - else if (previous == '/' && c == '>') - return "Closing double curly brackets not found, XML end node encountered first."; - - else if (previous == '<' && c == '/') - return "Closing double curly brackets not found, XML end node encountered first."; - } - } - else - { - if (c == '{' && previous == '{') - { - inside_brackets = true; - } - else if (c == '}' && previous == '}') - { - return "Closing double curly brackets encountered outside an expression."; - } - } - - return nullptr; -} - -} // namespace Rml +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "XMLParseTools.h" +#include "../../Include/RmlUi/Core/StreamMemory.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "TemplateCache.h" +#include "Template.h" +#include +#include + +namespace Rml { + +// Searchs a string for the specified tag +// NOTE: tag *MUST* be in lowercase +const char* XMLParseTools::FindTag(const char* tag, const char* string, bool closing_tag) +{ + const size_t length = strlen(tag); + const char* ptr = string; + bool found_closing = false; + + while (*ptr) + { + // Check if the first character matches + if (tolower((*ptr)) == tag[0]) + { + // If it does, check the whole word + if (StringUtilities::StringCompareCaseInsensitive(StringView(ptr, ptr + length), StringView(tag, tag + length))) + { + // Check for opening <, loop back in the string skipping white space and forward slashes if + // we're looking for the closing tag + const char* tag_start = ptr - 1; + while (tag_start > string && (StringUtilities::IsWhitespace(*tag_start) || *tag_start == '/')) + { + if (*tag_start == '/') + found_closing = true; + tag_start--; + } + + // If the character we're looking at is a <, and found closing matches closing tag, + // its the tag we're looking for + if (*tag_start == '<' && found_closing == closing_tag) + return tag_start; + + // Otherwise, keep looking + } + } + ptr++; + } + + return nullptr; +} + +bool XMLParseTools::ReadAttribute(const char* &string, String& name, String& value) +{ + const char* ptr = string; + + name = ""; + value = ""; + + // Skip whitespace + while (StringUtilities::IsWhitespace(*ptr)) + ptr++; + + // Look for the end of the attribute name + bool found_whitespace = false; + while (*ptr != '=' && *ptr != '>' && (!found_whitespace || StringUtilities::IsWhitespace(*ptr))) + { + if (StringUtilities::IsWhitespace(*ptr)) + found_whitespace = true; + else + name += *ptr; + ptr++; + } + if (*ptr == '>') + return false; + + // If we stopped on an equals, parse the value + if (*ptr == '=') + { + + // Skip over white space, ='s and quotes + bool quoted = false; + while (StringUtilities::IsWhitespace(*ptr) || *ptr == '\'' || *ptr == '"' || *ptr == '=') + { + if (*ptr == '\'' || *ptr == '"') + quoted = true; + ptr++; + } + if (*ptr == '>') + return false; + + // Store the value + while (*ptr != '\'' && *ptr != '"' && *ptr != '>' && (*ptr != ' ' || quoted)) + { + value += *ptr++; + } + if (*ptr == '>') + return false; + + // Advance passed the quote + if (quoted) + ptr++; + } + else + { + ptr--; + } + + // Update the string pointer + string = ptr; + + return true; +} + +Element* XMLParseTools::ParseTemplate(Element* element, const String& template_name) +{ + // Load the template, and parse it + Template* parse_template = TemplateCache::GetTemplate(template_name); + if (!parse_template) + { + Log::ParseError(element->GetOwnerDocument()->GetSourceURL(), -1, "Failed to find template '%s'.", template_name.c_str()); + return element; + } + + return parse_template->ParseTemplate(element); +} + +const char* XMLParseTools::ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous) +{ + if (inside_brackets) + { + if (c == '\'') + inside_string = !inside_string; + + if(!inside_string) + { + if (c == '}' && previous == '}') + inside_brackets = false; + + else if (c == '{' && previous == '{') + return "Nested double curly brackets are illegal."; + + else if (previous == '}' && c != '}') + return "Single closing curly bracket encountered, use double curly brackets to close an expression."; + + else if (previous == '/' && c == '>') + return "Closing double curly brackets not found, XML end node encountered first."; + + else if (previous == '<' && c == '/') + return "Closing double curly brackets not found, XML end node encountered first."; + } + } + else + { + if (c == '{' && previous == '{') + { + inside_brackets = true; + } + else if (c == '}' && previous == '}') + { + return "Closing double curly brackets encountered outside an expression."; + } + } + + return nullptr; +} + +} // namespace Rml