Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/system/ParamNode.cpp
/* Copyright (C) 2020 Wildfire Games. | /* Copyright (C) 2021 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 0 A.D. is free software: you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU General Public License for more details. | * GNU General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#include "precompiled.h" | #include "precompiled.h" | ||||
#include "ParamNode.h" | #include "ParamNode.h" | ||||
#include "lib/utf8.h" | #include "lib/utf8.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/CStr.h" | #include "ps/CStr.h" | ||||
#include "ps/Filesystem.h" | #include "ps/Filesystem.h" | ||||
#include "ps/ParamNodeLayer.h" | |||||
#include "ps/XML/Xeromyces.h" | #include "ps/XML/Xeromyces.h" | ||||
#include "scriptinterface/ScriptInterface.h" | #include "scriptinterface/ScriptInterface.h" | ||||
#include <sstream> | #include <sstream> | ||||
#include <boost/algorithm/string.hpp> | #include <boost/algorithm/string.hpp> | ||||
#include <boost/algorithm/string/join.hpp> // this isn't in string.hpp in old Boosts | #include <boost/algorithm/string/join.hpp> // this isn't in string.hpp in old Boosts | ||||
static CParamNode g_NullNode(false); | static CParamNode g_NullNode(false); | ||||
CParamNode::CParamNode(bool isOk) : | |||||
m_IsOk(isOk) | |||||
{ | |||||
} | |||||
void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb, const wchar_t* sourceIdentifier /*= NULL*/) | |||||
{ | |||||
ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier); | |||||
} | |||||
void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName) | |||||
{ | |||||
CXeromyces xero; | |||||
PSRETURN ok = xero.Load(g_VFS, path, validatorName); | |||||
if (ok != PSRETURN_OK) | |||||
return; // (Xeromyces already logged an error) | |||||
LoadXML(ret, xero, path.string().c_str()); | |||||
} | |||||
PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier /*=NULL*/) | |||||
{ | |||||
CXeromyces xero; | |||||
PSRETURN ok = xero.LoadString(xml); | |||||
if (ok != PSRETURN_OK) | |||||
return ok; | |||||
ret.ApplyLayer(xero, xero.GetRoot(), sourceIdentifier); | |||||
return PSRETURN_OK; | |||||
} | |||||
void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/) | |||||
{ | |||||
ResetScriptVal(); | |||||
std::string name = xmb.GetElementString(element.GetNodeName()); // TODO: is GetElementString inefficient? | |||||
CStrW value = element.GetText().FromUTF8(); | |||||
bool hasSetValue = false; | |||||
// Look for special attributes | |||||
int at_disable = xmb.GetAttributeID("disable"); | |||||
int at_replace = xmb.GetAttributeID("replace"); | |||||
int at_filtered = xmb.GetAttributeID("filtered"); | |||||
int at_merge = xmb.GetAttributeID("merge"); | |||||
int at_op = xmb.GetAttributeID("op"); | |||||
int at_datatype = xmb.GetAttributeID("datatype"); | |||||
enum op { | |||||
INVALID, | |||||
ADD, | |||||
MUL, | |||||
MUL_ROUND | |||||
} op = INVALID; | |||||
bool replacing = false; | |||||
bool filtering = false; | |||||
bool merging = false; | |||||
{ | |||||
XERO_ITER_ATTR(element, attr) | |||||
{ | |||||
if (attr.Name == at_disable) | |||||
{ | |||||
m_Childs.erase(name); | |||||
return; | |||||
} | |||||
else if (attr.Name == at_replace) | |||||
{ | |||||
m_Childs.erase(name); | |||||
replacing = true; | |||||
} | |||||
else if (attr.Name == at_filtered) | |||||
{ | |||||
filtering = true; | |||||
} | |||||
else if (attr.Name == at_merge) | |||||
{ | |||||
if (m_Childs.find(name) == m_Childs.end()) | |||||
return; | |||||
merging = true; | |||||
} | |||||
else if (attr.Name == at_op) | |||||
{ | |||||
if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add") | |||||
op = ADD; | |||||
else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul") | |||||
op = MUL; | |||||
else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul_round") | |||||
op = MUL_ROUND; | |||||
else | |||||
LOGWARNING("Invalid op '%ls'", attr.Value); | |||||
} | |||||
} | |||||
} | |||||
{ | |||||
XERO_ITER_ATTR(element, attr) | |||||
{ | |||||
if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens") | |||||
{ | |||||
CParamNode& node = m_Childs[name]; | |||||
// Split into tokens | |||||
std::vector<std::wstring> oldTokens; | |||||
std::vector<std::wstring> newTokens; | |||||
if (!replacing && !node.m_Value.empty()) // ignore the old tokens if replace="" was given | |||||
boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space(), boost::algorithm::token_compress_on); | |||||
if (!value.empty()) | |||||
boost::algorithm::split(newTokens, value, boost::algorithm::is_space(), boost::algorithm::token_compress_on); | |||||
// Merge the two lists | /** | ||||
std::vector<std::wstring> tokens = oldTokens; | * Apply a ParamNodeLayer from XML data specified by @a path into node @ret. | ||||
for (size_t i = 0; i < newTokens.size(); ++i) | * Any existing data in @a ret will be overwritten or else kept, so this | ||||
{ | * can be called multiple times to build up a node from multiple inputs. | ||||
if (newTokens[i][0] == L'-') | */ | ||||
{ | static bool ApplyXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName); | ||||
std::vector<std::wstring>::iterator tokenIt = std::find(tokens.begin(), tokens.end(), newTokens[i].substr(1)); | |||||
if (tokenIt != tokens.end()) | |||||
tokens.erase(tokenIt); | |||||
else | |||||
LOGWARNING("[ParamNode] Could not remove token '%s' from node '%s'%s; not present in list nor inherited (possible typo?)", | |||||
utf8_from_wstring(newTokens[i].substr(1)), name, sourceIdentifier ? (" in '" + utf8_from_wstring(sourceIdentifier) + "'").c_str() : ""); | |||||
} | |||||
else | |||||
{ | |||||
if (std::find(oldTokens.begin(), oldTokens.end(), newTokens[i]) == oldTokens.end()) | |||||
tokens.push_back(newTokens[i]); | |||||
} | |||||
} | |||||
node.m_Value = boost::algorithm::join(tokens, L" "); | |||||
hasSetValue = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
// Add this element as a child node | |||||
CParamNode& node = m_Childs[name]; | |||||
if (op != INVALID) | |||||
{ | |||||
// TODO: Support parsing of data types other than fixed; log warnings in other cases | |||||
fixed oldval = node.ToFixed(); | |||||
fixed mod = fixed::FromString(CStrW(value)); | |||||
switch (op) | |||||
{ | |||||
case ADD: | |||||
node.m_Value = (oldval + mod).ToString().FromUTF8(); | |||||
break; | |||||
case MUL: | |||||
node.m_Value = oldval.Multiply(mod).ToString().FromUTF8(); | |||||
break; | |||||
case MUL_ROUND: | |||||
node.m_Value = fixed::FromInt(oldval.Multiply(mod).ToInt_RoundToNearest()).ToString().FromUTF8(); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
hasSetValue = true; | |||||
} | |||||
if (!hasSetValue && !merging) | |||||
node.m_Value = value; | |||||
// We also need to reset node's script val, even if it has no children | |||||
// or if the attributes change. | |||||
node.ResetScriptVal(); | |||||
// For the filtered case | |||||
ChildrenMap childs; | |||||
// Recurse through the element's children | bool CParamNode::ApplyXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName) | ||||
XERO_ITER_EL(element, child) | |||||
{ | { | ||||
node.ApplyLayer(xmb, child, sourceIdentifier); | ParamNodeLayer layer; | ||||
if (filtering) | if (!layer.LoadXML(path, validatorName) || !layer.ApplyOnNode(ret)) | ||||
{ | return false; | ||||
std::string childname = xmb.GetElementString(child.GetNodeName()); | return true; | ||||
if (node.m_Childs.find(childname) != node.m_Childs.end()) | |||||
childs[childname] = std::move(node.m_Childs[childname]); | |||||
} | |||||
} | } | ||||
if (filtering) | CParamNode::CParamNode(bool isOk) : | ||||
node.m_Childs.swap(childs); | m_IsOk(isOk) | ||||
// Add the element's attributes, prefixing names with "@" | |||||
XERO_ITER_ATTR(element, attr) | |||||
{ | { | ||||
// Skip special attributes | |||||
if (attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered) | |||||
continue; | |||||
// Add any others | |||||
std::string attrName = xmb.GetAttributeString(attr.Name); | |||||
node.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8(); | |||||
} | |||||
} | } | ||||
const CParamNode& CParamNode::GetChild(const char* name) const | const CParamNode& CParamNode::GetChild(const char* name) const | ||||
{ | { | ||||
ChildrenMap::const_iterator it = m_Childs.find(name); | ChildrenMap::const_iterator it = m_Childs.find(name); | ||||
if (it == m_Childs.end()) | if (it == m_Childs.end()) | ||||
return g_NullNode; | return g_NullNode; | ||||
return it->second; | return it->second; | ||||
▲ Show 20 Lines • Show All 209 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator