Changeset View
Changeset View
Standalone View
Standalone View
source/ps/DataTreeXML.cpp
- This file was added.
/* Copyright (C) 2020 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
* | |||||
* 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 | |||||
* the Free Software Foundation, either version 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* 0 A.D. is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* 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/>. | |||||
*/ | |||||
#include "precompiled.h" | |||||
#include "DataTreeXML.h" | |||||
#include "maths/Fixed.h" | |||||
#include <boost/algorithm/string.hpp> | |||||
#include <boost/algorithm/string/join.hpp> // this isn't in string.hpp in old Boosts | |||||
bool DataTreeXMLImpl::LoadFile(const PIVFS& vfs, const VfsPath& filename) | |||||
{ | |||||
CVFSFile input; | |||||
if (input.Load(vfs, filename)) | |||||
{ | |||||
LOGERROR("DataTreeXML: Failed to open XML file %s", filename.string8()); | |||||
return false; | |||||
} | |||||
doc = xmlReadMemory((const char*)input.GetBuffer(), input.GetBufferSize(), CStrW(filename.string()).ToUTF8().c_str(), NULL, | |||||
XML_PARSE_NONET|XML_PARSE_NOCDATA); | |||||
return true; | |||||
} | |||||
bool DataTreeXMLImpl::IsIncludeNode(const nodetype& node) const | |||||
{ | |||||
return node->type == XML_ELEMENT_NODE && | |||||
(xmlStrEqual(node->name, (xmlChar*)"include") || | |||||
xmlHasProp(node, (xmlChar*)"parent") != nullptr); | |||||
; | |||||
} | |||||
VfsPath DataTreeXMLImpl::GetIncludePath(const PIVFS& UNUSED(vfs), const std::deque<VfsPath>& include_paths, const nodetype& node) const | |||||
{ | |||||
xmlChar* path = nullptr; | |||||
if (xmlStrEqual(node->name, (xmlChar*)"include")) | |||||
path = xmlNodeGetContent(node); | |||||
else | |||||
path = xmlGetProp(node, (xmlChar*)"parent"); | |||||
VfsPath ret = (include_paths.front() / (const char*)path).ChangeExtension(".xml"); | |||||
if (path) | |||||
xmlFree(path); | |||||
return ret; | |||||
} | |||||
bool DataTreeXMLImpl::IsReplaceNode(const nodetype& node) const | |||||
{ | |||||
return node->type == XML_ELEMENT_NODE && xmlHasProp(node, (xmlChar*)"replace") != nullptr; | |||||
} | |||||
bool DataTreeXMLImpl::CheckForInclude() const | |||||
{ | |||||
for (xmlNodePtr child = doc->children; child; child = child->next) | |||||
if (child->type == XML_PI_NODE && xmlStrEqual(child->name, (xmlChar*)"use_include")) | |||||
return true; | |||||
return false; | |||||
//nodetype root = xmlDocGetRootElement(doc); | |||||
//return IsIncludeNode(root); | |||||
} | |||||
DataTreeXMLImpl::nodetype DataTreeXMLImpl::GetRootNode(const doctype& doc) const | |||||
{ | |||||
return xmlDocGetRootElement(doc); | |||||
} | |||||
DataTreeXMLImpl::nodetype DataTreeXMLImpl::GetSameChild(const nodetype& node, const nodetype& similar_to) const | |||||
{ | |||||
for (xmlNodePtr child = node->children; child; child = child->next) | |||||
if (xmlStrEqual(child->name, similar_to->name)) | |||||
return child; | |||||
return nullptr; | |||||
} | |||||
void DataTreeXMLImpl::AddChild(nodetype& node, nodetype& child) | |||||
{ | |||||
xmlNodePtr add = xmlDocCopyNode(child, doc, 1); | |||||
xmlAddChild(node, add); | |||||
} | |||||
void DataTreeXMLImpl::DeleteNode(nodetype& node) | |||||
{ | |||||
xmlUnlinkNode(node); | |||||
xmlFreeNode(node); | |||||
} | |||||
void DataTreeXMLImpl::UpdateNode(nodetype& node_to_update, const nodetype& reference) | |||||
{ | |||||
if (xmlStrEqual(node_to_update->name, (xmlChar*)"include") || xmlHasProp(node_to_update, (xmlChar*)"parent") != nullptr) | |||||
{ | |||||
// Copy the node and its prop, but not children. | |||||
xmlNodePtr ref = xmlDocCopyNode(reference, doc, 2); | |||||
// Swap exitsting children so they're preserved. | |||||
for (xmlNodePtr child = node_to_update->children; child;) | |||||
{ | |||||
xmlNodePtr next = child->next; | |||||
if (child->type == XML_ELEMENT_NODE) | |||||
{ | |||||
xmlUnlinkNode(child); | |||||
xmlAddChild(ref, child); | |||||
} | |||||
child = next; | |||||
} | |||||
// Replace the node. | |||||
xmlAddNextSibling(node_to_update, ref); | |||||
xmlUnlinkNode(node_to_update); | |||||
xmlFreeNode(node_to_update); | |||||
node_to_update = ref; | |||||
return; | |||||
} | |||||
if (xmlHasProp(node_to_update, (xmlChar*)"op") != nullptr) | |||||
{ | |||||
xmlChar* op = xmlGetProp(node_to_update, (xmlChar*)"op"); | |||||
xmlChar* ref_val = xmlNodeGetContent(reference); | |||||
xmlChar* op_val = xmlNodeGetContent(node_to_update); | |||||
fixed original_value = fixed::FromString(CStr((const char*)ref_val)); | |||||
fixed new_value = fixed::FromString(CStr((const char*)op_val)); | |||||
if (xmlStrEqual(op, (xmlChar*)"add")) | |||||
new_value = original_value + new_value; | |||||
else if (xmlStrEqual(op, (xmlChar*)"mul")) | |||||
new_value = original_value.Multiply(new_value); | |||||
else if (xmlStrEqual(op, (xmlChar*)"mul_round")) | |||||
new_value = fixed::FromInt(original_value.Multiply(new_value).ToInt_RoundToNearest()); | |||||
else | |||||
LOGWARNING("Invalid op %s", (const char*)op_val); | |||||
xmlNodeSetContent(node_to_update, (xmlChar*)new_value.ToString().c_str()); | |||||
xmlFree(op_val); | |||||
xmlFree(ref_val); | |||||
xmlFree(op); | |||||
xmlUnsetProp(node_to_update, (xmlChar*)"op"); | |||||
return; | |||||
} | |||||
if (xmlHasProp(node_to_update, (xmlChar*)"datatype") != nullptr) | |||||
{ | |||||
xmlChar* datatype = xmlGetProp(node_to_update, (xmlChar*)"datatype"); | |||||
if (xmlStrEqual(datatype, (xmlChar*)"tokens")) | |||||
{ | |||||
// Split into tokens | |||||
std::vector<std::string> oldTokens; | |||||
std::vector<std::string> newTokens; | |||||
xmlChar* reference_content = xmlNodeGetContent(reference); | |||||
xmlChar* node_content = xmlNodeGetContent(node_to_update); | |||||
std::string old_tokens = std::string((const char*)reference_content); | |||||
std::string new_tokens = std::string((const char*)node_content); | |||||
boost::algorithm::split(oldTokens, old_tokens, boost::algorithm::is_space(), boost::algorithm::token_compress_on); | |||||
boost::algorithm::split(newTokens, new_tokens, boost::algorithm::is_space(), boost::algorithm::token_compress_on); | |||||
// Merge the two lists | |||||
std::vector<std::string> tokens = oldTokens; | |||||
for (const std::string& token : newTokens) | |||||
{ | |||||
if (token[0] == L'-') | |||||
{ | |||||
std::vector<std::string>::iterator tokenIt = std::find(tokens.begin(), tokens.end(), token.substr(1)); | |||||
if (tokenIt != tokens.end()) | |||||
tokens.erase(tokenIt); | |||||
else | |||||
LOGWARNING("[DataTreeXML] Could not remove token '%s' from node '%s'; not present in list nor inherited (possible typo?)", | |||||
token.substr(1), (const char*)node_to_update->name/*, sourceIdentifier ? (" in '" + utf8_from_wstring(sourceIdentifier) + "'").c_str() : ""*/); | |||||
} | |||||
else | |||||
{ | |||||
if (std::find(oldTokens.begin(), oldTokens.end(), token) == oldTokens.end()) | |||||
tokens.push_back(token); | |||||
} | |||||
} | |||||
xmlNodeSetContent(node_to_update, (xmlChar*)boost::algorithm::join(tokens, L" ").c_str()); | |||||
xmlFree(node_content); | |||||
xmlFree(reference_content); | |||||
} | |||||
xmlFree(datatype); | |||||
return; | |||||
} | |||||
} | |||||
void DataTreeXMLImpl::PostMergeProcessing(nodetype& node) | |||||
{ | |||||
if (xmlHasProp(node, (xmlChar*)"replace") != nullptr) | |||||
xmlUnsetProp(node, (xmlChar*)"replace"); | |||||
if (xmlHasProp(node, (xmlChar*)"disable") != nullptr) | |||||
{ | |||||
// Disable node -> drop along with its children. | |||||
// We need to point node to the next sibling, if any. | |||||
nodetype next = node->next; | |||||
DeleteNode(node); | |||||
node = next; | |||||
} | |||||
if (xmlHasProp(node, (xmlChar*)"temporary") != nullptr) | |||||
{ | |||||
// The node was a temporary node that needs to be dropped. | |||||
xmlNodePtr newnode = nullptr; | |||||
for (xmlNodePtr child = node->children; child;) | |||||
{ | |||||
xmlNodePtr temp = child->next; | |||||
newnode = xmlAddPrevSibling(node, child); | |||||
child = temp; | |||||
} | |||||
DeleteNode(node); | |||||
node = newnode; | |||||
} | |||||
} |
Wildfire Games · Phabricator