/*
* 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 "precompiled.h"
#include "../../Include/RmlUi/Core/DataModel.h"
namespace Rml {
namespace Core {
static Address ParseAddress(const String& address_str)
{
StringList list;
StringUtilities::ExpandString(list, address_str, '.');
Address address;
address.reserve(list.size() * 2);
for (const auto& item : list)
{
if (item.empty())
return Address();
size_t i_open = item.find('[', 0);
if (i_open == 0)
return Address();
address.emplace_back(item.substr(0, i_open));
while (i_open != String::npos)
{
size_t i_close = item.find(']', i_open + 1);
if (i_close == String::npos)
return Address();
int index = FromString(item.substr(i_open + 1, i_close - i_open), -1);
if (index < 0)
return Address();
address.emplace_back(index);
i_open = item.find('[', i_close + 1);
}
// TODO: Abort on invalid characters among [ ] and after the last found bracket?
}
return address;
};
bool DataModel::Bind(const String& name, void* ptr, VariableDefinition* variable)
{
if (!variable)
{
Log::Message(Log::LT_WARNING, "No registered type could be found for the data variable '%s'.", name.c_str());
return false;
}
RMLUI_ASSERT(ptr || variable->Type() == VariableType::Function);
bool inserted = variables.emplace(name, Variable(variable, ptr)).second;
if (!inserted)
{
Log::Message(Log::LT_WARNING, "Data model variable with name '%s' already exists.", name.c_str());
return false;
}
return true;
}
Variable DataModel::GetVariable(const String& address_str) const
{
Address address = ParseAddress(address_str);
if (address.empty() || address.front().name.empty())
{
Log::Message(Log::LT_WARNING, "Invalid data address '%s'.", address_str.c_str());
return Variable();
}
Variable instance = GetVariable(address);
if (!instance)
{
Log::Message(Log::LT_WARNING, "Could not find the data variable '%s'.", address_str.c_str());
return Variable();
}
return instance;
}
Variable DataModel::GetVariable(const Address& address) const
{
if (address.empty() || address.front().name.empty())
return Variable();
auto it = variables.find(address.front().name);
if (it == variables.end())
return Variable();
Variable variable = it->second;
for (int i = 1; i < (int)address.size() && variable; i++)
{
variable = variable.GetChild(address[i]);
if (!variable)
return Variable();
}
return variable;
}
void DataModel::DirtyVariable(const String& variable_name)
{
RMLUI_ASSERTMSG(variables.count(variable_name) == 1, "Variable name not found among added variables.");
dirty_variables.insert(variable_name);
}
bool DataModel::IsVariableDirty(const String& variable_name) const {
return (dirty_variables.count(variable_name) == 1);
}
Address DataModel::ResolveAddress(const String& address_str, Element* parent) const
{
Address address = ParseAddress(address_str);
if (address.empty() || address.front().name.empty())
return Address();
const String& first_name = address.front().name;
auto it = variables.find(first_name);
if (it != variables.end())
return address;
// Look for a variable alias for the first name.
Element* ancestor = parent;
while (ancestor && ancestor->GetDataModel() == this)
{
auto it_element = aliases.find(ancestor);
if (it_element != aliases.end())
{
auto& alias_names = it_element->second;
auto it_alias_name = alias_names.find(first_name);
if (it_alias_name != alias_names.end())
{
const Address& replace_address = it_alias_name->second;
if (replace_address.empty() || replace_address.front().name.empty())
{
// Variable alias is invalid
return Address();
}
// Insert the full alias address, replacing the first element.
address[0] = std::move(replace_address[0]);
address.insert(address.begin() + 1, replace_address.begin() + 1, replace_address.end());
return address;
}
}
ancestor = ancestor->GetParentNode();
}
Log::Message(Log::LT_WARNING, "Could not find variable name '%s' in data model.", address_str.c_str());
return Address();
}
bool DataModel::InsertAlias(Element* element, const String& alias_name, Address replace_with_address) const
{
if (replace_with_address.empty() || replace_with_address.front().name.empty())
{
Log::Message(Log::LT_WARNING, "Could not add alias variable '%s' to data model, replacement address invalid.", alias_name.c_str());
return false;
}
auto& map = aliases.emplace(element, SmallUnorderedMap()).first->second;
auto it = map.find(alias_name);
if (it != map.end())
Log::Message(Log::LT_WARNING, "Alias name '%s' in data model already exists, replaced.", alias_name.c_str());
map[alias_name] = std::move(replace_with_address);
return true;
}
bool DataModel::EraseAliases(Element* element) const
{
return aliases.erase(element) == 1;
}
bool DataModel::Update()
{
bool result = views.Update(*this, dirty_variables);
dirty_variables.clear();
return result;
}
void DataModel::OnElementRemove(Element* element)
{
EraseAliases(element);
views.OnElementRemove(element);
}
#ifdef RMLUI_DEBUG
static struct TestDataVariables {
TestDataVariables()
{
using IntVector = std::vector;
struct FunData {
int i = 99;
String x = "hello";
IntVector magic = { 3, 5, 7, 11, 13 };
};
using FunArray = std::array;
struct SmartData {
bool valid = true;
FunData fun;
FunArray more_fun;
};
DataModel model;
DataTypeRegister types;
DataModelHandle handle(&model, &types);
{
handle.RegisterArray();
if (auto fun_handle = handle.RegisterStruct())
{
fun_handle.AddMember("i", &FunData::i);
fun_handle.AddMember("x", &FunData::x);
fun_handle.AddMember("magic", &FunData::magic);
}
handle.RegisterArray();
if (auto smart_handle = handle.RegisterStruct())
{
smart_handle.AddMember("valid", &SmartData::valid);
smart_handle.AddMember("fun", &SmartData::fun);
smart_handle.AddMember("more_fun", &SmartData::more_fun);
}
}
SmartData data;
data.fun.x = "Hello, we're in SmartData!";
handle.Bind("data", &data);
{
std::vector test_addresses = { "data.more_fun[1].magic[3]", "data.fun.x", "data.valid" };
std::vector expected_results = { ToString(data.more_fun[1].magic[3]), ToString(data.fun.x), ToString(data.valid) };
std::vector results;
for (auto& str_address : test_addresses)
{
Address address = ParseAddress(str_address);
String result;
if(model.GetValue(address, result))
results.push_back(result);
}
RMLUI_ASSERT(results == expected_results);
bool success = model.GetVariable("data.more_fun[1].magic[1]").Set(Variant(String("199")));
RMLUI_ASSERT(success && data.more_fun[1].magic[1] == 199);
data.fun.magic = { 99, 190, 55, 2000, 50, 60, 70, 80, 90 };
Variant test_get_result;
bool test_get_success = model.GetVariable("data.fun.magic[8]").Get(test_get_result);
RMLUI_ASSERT(test_get_success && test_get_result.Get() == "90");
}
}
} test_data_variables;
#endif
}
}