Skip to content

Commit

Permalink
Unify parsing of curly brackets in RML.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikke89 committed Mar 9, 2020
1 parent ca1dd1d commit c65ebf6
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 85 deletions.
4 changes: 2 additions & 2 deletions Include/RmlUi/Core/Factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ class RMLUICORE_API Factory
/// @return The instanced element, or nullptr if the instancing failed.
static ElementPtr InstanceElement(Element* parent, const String& instancer, const String& tag, const XMLAttributes& attributes);

/// Instances a single text element containing a string. The string is assumed to contain no RML markup, but will
/// be translated and therefore may have some introduced. In this case more than one element may be instanced.
/// Instances a text element containing a string.
/// More than one element may be instanced if the string contains RML or RML is introduced during translation.
/// @param[in] parent The element any instanced elements will be parented to.
/// @param[in] text The text to instance the element (or elements) from.
/// @return True if the string was parsed without error, false otherwise.
Expand Down
2 changes: 1 addition & 1 deletion Samples/basic/databinding/data/databinding.rml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ form h2
<img sprite="icon-invader" data-style-image-color="rating < 80 ? 'black' : 'green'"/>
<p>
For loop with data expressions:<br/>
<span style="padding-left: 1em;" data-for="i : list"> {{ i * 2 + (i > 10 ? ' wow!' | to_upper : '') }}</span>
<span style="padding-left: 1em;" data-for="i : list"> {{ i * 2 + (!(i < 10) ? ' wow!' | to_upper : '') }}</span>
</p>
</panel>
<tab>Invaders</tab>
Expand Down
11 changes: 7 additions & 4 deletions Source/Core/BaseXMLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "../../Include/RmlUi/Core/BaseXMLParser.h"
#include "../../Include/RmlUi/Core/Profiling.h"
#include "../../Include/RmlUi/Core/Stream.h"
#include "XMLParseTools.h"
#include <string.h>

namespace Rml {
Expand Down Expand Up @@ -488,10 +489,12 @@ bool BaseXMLParser::FindString(const char* string, String& data, bool escape_bra

if(escape_brackets)
{
if (c == '{' && previous == '{')
in_brackets = true;
else if (c == '}' && previous == '}')
in_brackets = false;
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)
Expand Down
113 changes: 35 additions & 78 deletions Source/Core/Factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "../../Include/RmlUi/Core/Factory.h"
#include "../../Include/RmlUi/Core/StreamMemory.h"
#include "../../Include/RmlUi/Core/SystemInterface.h"
#include "../../Include/RmlUi/Core.h"

#include "ContextInstancerDefault.h"
Expand Down Expand Up @@ -57,6 +58,7 @@
#include "XMLNodeHandlerHead.h"
#include "XMLNodeHandlerTemplate.h"
#include "XMLParseTools.h"
#include <algorithm>

namespace Rml {
namespace Core {
Expand Down Expand Up @@ -299,95 +301,50 @@ ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_nam
return nullptr;
}

struct ElementTextTraits {
bool only_white_space = true;
bool has_xml = false;
bool has_curly_brackets = false;
const char* error_str = nullptr; // Is set if and only if a parse error occurred.
};
static ElementTextTraits ParseElementTextTraits(const String& text)
// Instances a single text element containing a string.
bool Factory::InstanceElementText(Element* parent, const String& in_text)
{
ElementTextTraits result;

bool in_brackets = false;
char previous = 0;
for (const char c : text)
{
if (!StringUtilities::IsWhitespace(c))
result.only_white_space = false;
RMLUI_ASSERT(parent);

if (in_brackets)
{
if (c == '}' && previous == '}')
in_brackets = false;

else if (c == '{' && previous == '{')
result.error_str = "Nested double curly brackets are illegal.";
String text;
if (SystemInterface* system_interface = GetSystemInterface())
system_interface->TranslateString(text, in_text);

else if (previous == '}' && c != '}')
result.error_str = "Single closing curly bracket encountered, use double curly brackets to close data expression.";
// If this text node only contains white-space we don't want to construct it.
const bool only_white_space = std::all_of(text.begin(), text.end(), &StringUtilities::IsWhitespace);
if (only_white_space)
return true;

else if (previous == '/' && c == '>')
result.error_str = "Xml end node encountered inside double curly brackets.";
// See if we need to parse it as RML, and whether the text contains data expressions (curly brackets).
bool parse_as_rml = false;
bool has_data_expression = false;

else if (previous == '<' && c == '/')
result.error_str = "Xml end node encountered inside double curly brackets.";
}
else
bool inside_brackets = false;
char previous = 0;
for (const char c : text)
{
const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, c, previous);
if (error_str)
{
if(c == '<')
{
result.has_xml = true;
}
else if (c == '{' && previous == '{')
{
in_brackets = true;
result.has_curly_brackets = true;
}
else if (c == '}' && previous == '}')
{
result.error_str = "Closing double curly brackets encountered outside data expression.";
}
Log::Message(Log::LT_WARNING, "Failed to instance text element '%s'. %s", text.c_str(), error_str);
return false;
}

if (result.error_str)
break;
if (inside_brackets)
has_data_expression = true;
else if (c == '<')
parse_as_rml = true;

previous = c;
}

return result;
}

// Instances a single text element containing a string.
bool Factory::InstanceElementText(Element* parent, const String& text)
{
RMLUI_ASSERT(parent);

String translated_data;
if (SystemInterface* system_interface = GetSystemInterface())
system_interface->TranslateString(translated_data, text);

// Look for XML tags and detect double curly brackets for data bindings.
ElementTextTraits traits = ParseElementTextTraits(text);

if (traits.error_str)
{
Log::Message(Log::LT_ERROR, "Parse error in text: %s In element: %s", traits.error_str, parent->GetAddress().c_str());
return false;
}

// If this text node only contains white-space we don't want to construct it.
if (traits.only_white_space)
return true;

// If the text contains XML elements then run it through the XML parser again.
if (traits.has_xml)
// If the text contains RML elements then run it through the XML parser again.
if (parse_as_rml)
{
RMLUI_ZoneScopedNC("InstanceStream", 0xDC143C);
auto stream = std::make_unique<StreamMemory>(translated_data.size() + 32);
auto stream = std::make_unique<StreamMemory>(text.size() + 32);
stream->Write("<body>", 6);
stream->Write(translated_data);
stream->Write(text);
stream->Write("</body>", 7);
stream->Seek(0, SEEK_SET);

Expand All @@ -401,25 +358,25 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
XMLAttributes attributes;

// If we have curly brackets in the text, we tag the element so that the appropriate data view (DataViewText) is constructed.
if(traits.has_curly_brackets)
if (has_data_expression)
attributes.emplace("data-text", Variant());

ElementPtr element = Factory::InstanceElement(parent, "#text", "#text", attributes);
if (!element)
{
Log::Message(Log::LT_ERROR, "Failed to instance text element '%s', instancer returned nullptr.", translated_data.c_str());
Log::Message(Log::LT_ERROR, "Failed to instance text element '%s', instancer returned nullptr.", text.c_str());
return false;
}

// Assign the element its text value.
ElementText* text_element = rmlui_dynamic_cast< ElementText* >(element.get());
if (!text_element)
{
Log::Message(Log::LT_ERROR, "Failed to instance text element '%s'. Found type '%s', was expecting a derivative of ElementText.", translated_data.c_str(), rmlui_type_name(*element));
Log::Message(Log::LT_ERROR, "Failed to instance text element '%s'. Found type '%s', was expecting a derivative of ElementText.", text.c_str(), rmlui_type_name(*element));
return false;
}

text_element->SetText(translated_data);
text_element->SetText(text);

// Add to active node.
parent->AppendChild(std::move(element));
Expand Down
34 changes: 34 additions & 0 deletions Source/Core/XMLParseTools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,39 @@ Element* XMLParseTools::ParseTemplate(Element* element, const String& template_n
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;
}

}
}
6 changes: 6 additions & 0 deletions Source/Core/XMLParseTools.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ class XMLParseTools
/// @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);
};

}
Expand Down

0 comments on commit c65ebf6

Please sign in to comment.