2010-01-09 20:20:14 +01:00
|
|
|
/* Copyright (C) 2010 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 "ParamNode.h"
|
|
|
|
|
2011-02-17 21:08:20 +01:00
|
|
|
#include "lib/utf8.h"
|
2010-05-02 22:32:37 +02:00
|
|
|
#include "ps/CStr.h"
|
2010-10-23 21:59:40 +02:00
|
|
|
#include "ps/Filesystem.h"
|
2010-01-09 20:20:14 +01:00
|
|
|
#include "ps/XML/Xeromyces.h"
|
|
|
|
|
2010-05-07 02:24:22 +02:00
|
|
|
#include "js/jsapi.h"
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
#include <sstream>
|
2010-07-03 12:48:44 +02:00
|
|
|
|
|
|
|
// Disable "'boost::algorithm::detail::is_classifiedF' : assignment operator could not be generated"
|
|
|
|
#if MSC_VERSION
|
|
|
|
#pragma warning(disable:4512)
|
|
|
|
#endif
|
|
|
|
|
2010-07-03 03:23:23 +02:00
|
|
|
#include <boost/algorithm/string.hpp>
|
2010-07-03 12:48:44 +02:00
|
|
|
#include <boost/algorithm/string/join.hpp> // this isn't in string.hpp in old Boosts
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-02-03 00:01:17 +01:00
|
|
|
static CParamNode g_NullNode(false);
|
|
|
|
|
|
|
|
CParamNode::CParamNode(bool isOk) :
|
|
|
|
m_IsOk(isOk)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb)
|
|
|
|
{
|
|
|
|
ret.ApplyLayer(xmb, xmb.GetRoot());
|
|
|
|
}
|
|
|
|
|
2010-10-23 21:59:40 +02:00
|
|
|
void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path)
|
|
|
|
{
|
|
|
|
CXeromyces xero;
|
|
|
|
PSRETURN ok = xero.Load(g_VFS, path);
|
|
|
|
if (ok != PSRETURN_OK)
|
|
|
|
return; // (Xeromyces already logged an error)
|
|
|
|
|
|
|
|
LoadXML(ret, xero);
|
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml)
|
|
|
|
{
|
|
|
|
CXeromyces xero;
|
|
|
|
PSRETURN ok = xero.LoadString(xml);
|
|
|
|
if (ok != PSRETURN_OK)
|
|
|
|
return ok;
|
|
|
|
|
|
|
|
ret.ApplyLayer(xero, xero.GetRoot());
|
|
|
|
|
|
|
|
return PSRETURN_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element)
|
|
|
|
{
|
2010-07-21 18:04:17 +02:00
|
|
|
ResetScriptVal();
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
std::string name = xmb.GetElementString(element.GetNodeName()); // TODO: is GetElementString inefficient?
|
2011-02-17 21:08:20 +01:00
|
|
|
CStrW value = element.GetText().FromUTF8();
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-07-03 03:23:23 +02:00
|
|
|
bool hasSetValue = false;
|
|
|
|
|
2010-02-12 23:48:16 +01:00
|
|
|
// Look for special attributes
|
2010-04-09 20:43:50 +02:00
|
|
|
int at_disable = xmb.GetAttributeID("disable");
|
|
|
|
int at_replace = xmb.GetAttributeID("replace");
|
2010-07-03 03:23:23 +02:00
|
|
|
int at_datatype = xmb.GetAttributeID("datatype");
|
2011-07-07 19:05:22 +02:00
|
|
|
bool replacing = false;
|
2010-02-12 23:48:16 +01:00
|
|
|
{
|
|
|
|
XERO_ITER_ATTR(element, attr)
|
|
|
|
{
|
|
|
|
if (attr.Name == at_disable)
|
|
|
|
{
|
|
|
|
m_Childs.erase(name);
|
|
|
|
return;
|
|
|
|
}
|
2010-04-09 20:43:50 +02:00
|
|
|
else if (attr.Name == at_replace)
|
|
|
|
{
|
|
|
|
m_Childs.erase(name);
|
2011-07-07 19:05:22 +02:00
|
|
|
replacing = true;
|
2010-04-09 20:43:50 +02:00
|
|
|
}
|
2011-07-07 19:05:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
XERO_ITER_ATTR(element, attr)
|
|
|
|
{
|
|
|
|
if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens")
|
2010-07-03 03:23:23 +02:00
|
|
|
{
|
|
|
|
CParamNode& node = m_Childs[name];
|
2010-07-03 17:21:50 +02:00
|
|
|
|
|
|
|
// Split into tokens
|
2010-07-03 03:23:23 +02:00
|
|
|
std::vector<std::wstring> oldTokens;
|
|
|
|
std::vector<std::wstring> newTokens;
|
2011-07-07 19:05:22 +02:00
|
|
|
if (!replacing) // ignore the old tokens if replace="" was given
|
|
|
|
boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space());
|
2011-02-17 21:08:20 +01:00
|
|
|
boost::algorithm::split(newTokens, value, boost::algorithm::is_space());
|
2010-07-03 17:21:50 +02:00
|
|
|
|
|
|
|
// Delete empty tokens
|
|
|
|
oldTokens.erase(std::remove_if(oldTokens.begin(), oldTokens.end(), std::mem_fun_ref(&std::wstring::empty)), oldTokens.end());
|
|
|
|
newTokens.erase(std::remove_if(newTokens.begin(), newTokens.end(), std::mem_fun_ref(&std::wstring::empty)), newTokens.end());
|
2010-07-03 03:23:23 +02:00
|
|
|
|
2010-07-03 17:21:50 +02:00
|
|
|
// Merge the two lists
|
2010-07-03 03:23:23 +02:00
|
|
|
std::vector<std::wstring> tokens = oldTokens;
|
|
|
|
for (size_t i = 0; i < newTokens.size(); ++i)
|
|
|
|
{
|
|
|
|
if (newTokens[i][0] == L'-')
|
|
|
|
tokens.erase(std::find(tokens.begin(), tokens.end(), newTokens[i].substr(1)));
|
|
|
|
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;
|
|
|
|
}
|
2010-02-12 23:48:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
// Add this element as a child node
|
|
|
|
CParamNode& node = m_Childs[name];
|
2010-07-03 03:23:23 +02:00
|
|
|
if (!hasSetValue)
|
2011-02-17 21:08:20 +01:00
|
|
|
node.m_Value = value;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
// Recurse through the element's children
|
|
|
|
XERO_ITER_EL(element, child)
|
|
|
|
{
|
|
|
|
node.ApplyLayer(xmb, child);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the element's attributes, prefixing names with "@"
|
|
|
|
XERO_ITER_ATTR(element, attr)
|
|
|
|
{
|
2010-04-09 20:43:50 +02:00
|
|
|
// Skip special attributes
|
|
|
|
if (attr.Name == at_replace) continue;
|
|
|
|
// Add any others
|
2010-01-09 20:20:14 +01:00
|
|
|
std::string attrName = xmb.GetAttributeString(attr.Name);
|
2011-02-17 21:08:20 +01:00
|
|
|
node.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-14 21:36:29 +01:00
|
|
|
void CParamNode::CopyFilteredChildrenOfChild(const CParamNode& src, const char* name, const std::set<std::string>& permitted)
|
|
|
|
{
|
2010-07-21 18:04:17 +02:00
|
|
|
ResetScriptVal();
|
|
|
|
|
2010-01-14 21:36:29 +01:00
|
|
|
ChildrenMap::iterator dstChild = m_Childs.find(name);
|
|
|
|
ChildrenMap::const_iterator srcChild = src.m_Childs.find(name);
|
|
|
|
if (dstChild == m_Childs.end() || srcChild == src.m_Childs.end())
|
|
|
|
return; // error
|
|
|
|
|
|
|
|
ChildrenMap::const_iterator it = srcChild->second.m_Childs.begin();
|
|
|
|
for (; it != srcChild->second.m_Childs.end(); ++it)
|
|
|
|
if (permitted.count(it->first))
|
|
|
|
dstChild->second.m_Childs[it->first] = it->second;
|
|
|
|
}
|
|
|
|
|
2010-02-03 00:01:17 +01:00
|
|
|
const CParamNode& CParamNode::GetChild(const char* name) const
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
ChildrenMap::const_iterator it = m_Childs.find(name);
|
|
|
|
if (it == m_Childs.end())
|
2010-02-03 00:01:17 +01:00
|
|
|
return g_NullNode;
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CParamNode::IsOk() const
|
|
|
|
{
|
|
|
|
return m_IsOk;
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const std::wstring& CParamNode::ToString() const
|
|
|
|
{
|
|
|
|
return m_Value;
|
|
|
|
}
|
|
|
|
|
2011-02-17 21:08:20 +01:00
|
|
|
const std::string CParamNode::ToUTF8() const
|
2010-05-28 01:31:03 +02:00
|
|
|
{
|
2011-02-17 21:08:20 +01:00
|
|
|
return utf8_from_wstring(m_Value);
|
2010-05-28 01:31:03 +02:00
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
int CParamNode::ToInt() const
|
|
|
|
{
|
2010-02-03 00:01:17 +01:00
|
|
|
int ret = 0;
|
2010-01-09 20:20:14 +01:00
|
|
|
std::wstringstream strm;
|
|
|
|
strm << m_Value;
|
|
|
|
strm >> ret;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-05-02 22:32:37 +02:00
|
|
|
fixed CParamNode::ToFixed() const
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-05-02 22:32:37 +02:00
|
|
|
return fixed::FromString(CStrW(m_Value));
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CParamNode::ToBool() const
|
|
|
|
{
|
|
|
|
if (m_Value == L"true")
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const CParamNode::ChildrenMap& CParamNode::GetChildren() const
|
|
|
|
{
|
|
|
|
return m_Childs;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::wstring CParamNode::EscapeXMLString(const std::wstring& str)
|
|
|
|
{
|
|
|
|
std::wstring ret;
|
|
|
|
ret.reserve(str.size());
|
|
|
|
for (size_t i = 0; i < str.size(); ++i)
|
|
|
|
{
|
|
|
|
wchar_t c = str[i];
|
|
|
|
switch (c)
|
|
|
|
{
|
|
|
|
case '<': ret += L"<"; break;
|
|
|
|
case '>': ret += L">"; break;
|
|
|
|
case '&': ret += L"&"; break;
|
|
|
|
case '"': ret += L"""; break;
|
2010-04-09 20:43:50 +02:00
|
|
|
case '\t': ret += L"	"; break;
|
|
|
|
case '\n': ret += L" "; break;
|
|
|
|
case '\r': ret += L" "; break;
|
2010-01-09 20:20:14 +01:00
|
|
|
default:
|
2010-04-09 20:43:50 +02:00
|
|
|
if ((0x20 <= c && c <= 0xD7FF) || (0xE000 <= c && c <= 0xFFFD))
|
2010-01-09 20:20:14 +01:00
|
|
|
ret += c;
|
|
|
|
else
|
|
|
|
ret += 0xFFFD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::wstring CParamNode::ToXML() const
|
|
|
|
{
|
|
|
|
std::wstringstream strm;
|
|
|
|
ToXML(strm);
|
|
|
|
return strm.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CParamNode::ToXML(std::wostream& strm) const
|
|
|
|
{
|
|
|
|
strm << m_Value;
|
|
|
|
|
|
|
|
ChildrenMap::const_iterator it = m_Childs.begin();
|
|
|
|
for (; it != m_Childs.end(); ++it)
|
|
|
|
{
|
|
|
|
// Skip attributes here (they were handled when the caller output the tag)
|
|
|
|
if (it->first.length() && it->first[0] == '@')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::wstring name (it->first.begin(), it->first.end());
|
|
|
|
|
|
|
|
strm << L"<" << name;
|
|
|
|
|
|
|
|
// Output the child's attributes first
|
|
|
|
ChildrenMap::const_iterator cit = it->second.m_Childs.begin();
|
|
|
|
for (; cit != it->second.m_Childs.end(); ++cit)
|
|
|
|
{
|
|
|
|
if (cit->first.length() && cit->first[0] == '@')
|
|
|
|
{
|
|
|
|
std::wstring attrname (cit->first.begin()+1, cit->first.end());
|
|
|
|
strm << L" " << attrname << L"=\"" << EscapeXMLString(cit->second.m_Value) << L"\"";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
strm << L">";
|
|
|
|
|
|
|
|
it->second.ToXML(strm);
|
|
|
|
|
|
|
|
strm << L"</" << name << ">";
|
|
|
|
}
|
|
|
|
}
|
2010-05-07 02:24:22 +02:00
|
|
|
|
2011-01-12 13:29:00 +01:00
|
|
|
jsval CParamNode::ToJSVal(JSContext* cx, bool cacheValue) const
|
2010-05-07 02:24:22 +02:00
|
|
|
{
|
2011-01-12 13:29:00 +01:00
|
|
|
if (cacheValue && !m_ScriptVal.uninitialised())
|
|
|
|
return m_ScriptVal.get();
|
|
|
|
|
|
|
|
jsval val = ConstructJSVal(cx);
|
|
|
|
|
|
|
|
if (cacheValue)
|
|
|
|
m_ScriptVal = CScriptValRooted(cx, val);
|
|
|
|
|
|
|
|
return val;
|
2010-05-07 02:24:22 +02:00
|
|
|
}
|
|
|
|
|
2011-01-12 13:29:00 +01:00
|
|
|
jsval CParamNode::ConstructJSVal(JSContext* cx) const
|
2010-05-07 02:24:22 +02:00
|
|
|
{
|
2011-01-12 13:29:00 +01:00
|
|
|
if (m_Childs.empty())
|
|
|
|
{
|
|
|
|
// Empty node - map to undefined
|
|
|
|
if (m_Value.empty())
|
|
|
|
return JSVAL_VOID;
|
|
|
|
|
|
|
|
// Just a string
|
|
|
|
utf16string text(m_Value.begin(), m_Value.end());
|
|
|
|
JSString* str = JS_InternUCStringN(cx, reinterpret_cast<const jschar*>(text.data()), text.length());
|
|
|
|
if (str)
|
|
|
|
return STRING_TO_JSVAL(str);
|
|
|
|
// TODO: report error
|
|
|
|
return JSVAL_VOID;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Got child nodes - convert this node into a hash-table-style object:
|
|
|
|
|
|
|
|
JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
|
|
|
|
if (!obj)
|
|
|
|
return JSVAL_VOID; // TODO: report error
|
|
|
|
|
|
|
|
for (std::map<std::string, CParamNode>::const_iterator it = m_Childs.begin(); it != m_Childs.end(); ++it)
|
|
|
|
{
|
|
|
|
jsval childVal = it->second.ConstructJSVal(cx);
|
|
|
|
if (!JS_SetProperty(cx, obj, it->first.c_str(), &childVal))
|
|
|
|
return JSVAL_VOID; // TODO: report error
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the node has a string too, add that as an extra property
|
|
|
|
if (!m_Value.empty())
|
|
|
|
{
|
|
|
|
utf16string text(m_Value.begin(), m_Value.end());
|
|
|
|
JSString* str = JS_InternUCStringN(cx, reinterpret_cast<const jschar*>(text.data()), text.length());
|
|
|
|
if (!str)
|
|
|
|
return JSVAL_VOID; // TODO: report error
|
|
|
|
jsval childVal = STRING_TO_JSVAL(str);
|
|
|
|
if (!JS_SetProperty(cx, obj, "_string", &childVal))
|
|
|
|
return JSVAL_VOID; // TODO: report error
|
|
|
|
}
|
|
|
|
|
|
|
|
return OBJECT_TO_JSVAL(obj);
|
2010-05-07 02:24:22 +02:00
|
|
|
}
|
2010-07-21 18:04:17 +02:00
|
|
|
|
|
|
|
void CParamNode::ResetScriptVal()
|
|
|
|
{
|
|
|
|
m_ScriptVal = CScriptValRooted();
|
|
|
|
}
|