LCOV - code coverage report
Current view: top level - source/simulation2/system - ParamNode.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 185 205 90.2 %
Date: 2022-06-14 00:41:00 Functions: 17 21 81.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "ParamNode.h"
      21             : 
      22             : #include "lib/utf8.h"
      23             : #include "ps/CLogger.h"
      24             : #include "ps/CStr.h"
      25             : #include "ps/CStrIntern.h"
      26             : #include "ps/Filesystem.h"
      27             : #include "ps/XML/Xeromyces.h"
      28             : #include "scriptinterface/ScriptRequest.h"
      29             : 
      30             : #include <sstream>
      31             : 
      32             : #include <boost/algorithm/string.hpp>
      33             : 
      34             : static CParamNode g_NullNode(false);
      35             : 
      36         538 : CParamNode::CParamNode(bool isOk) :
      37        2690 :     m_IsOk(isOk)
      38             : {
      39         127 : }
      40             : 
      41          31 : void CParamNode::LoadXML(CParamNode& ret, const XMBData& xmb, const wchar_t* sourceIdentifier /*= NULL*/)
      42             : {
      43          40 :     ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier);
      44          31 : }
      45             : 
      46           9 : void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName)
      47             : {
      48          18 :     CXeromyces xero;
      49           9 :     PSRETURN ok = xero.Load(g_VFS, path, validatorName);
      50           9 :     if (ok != PSRETURN_OK)
      51           0 :         return; // (Xeromyces already logged an error)
      52             : 
      53          36 :     LoadXML(ret, xero, path.string().c_str());
      54             : }
      55             : 
      56          54 : PSRETURN CParamNode::LoadXMLString(CParamNode& ret, const char* xml, const wchar_t* sourceIdentifier /*=NULL*/)
      57             : {
      58         162 :     CXeromyces xero;
      59         108 :     PSRETURN ok = xero.LoadString(xml);
      60          54 :     if (ok != PSRETURN_OK)
      61             :         return ok;
      62             : 
      63          54 :     ret.ApplyLayer(xero, xero.GetRoot(), sourceIdentifier);
      64             : 
      65          54 :     return PSRETURN_OK;
      66             : }
      67             : 
      68         476 : void CParamNode::ApplyLayer(const XMBData& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/)
      69             : {
      70         952 :     ResetScriptVal();
      71             : 
      72         930 :     std::string name = xmb.GetElementString(element.GetNodeName());
      73         930 :     CStr value = element.GetText();
      74             : 
      75         476 :     bool hasSetValue = false;
      76             : 
      77             :     // Look for special attributes
      78         476 :     int at_disable = xmb.GetAttributeID("disable");
      79         476 :     int at_replace = xmb.GetAttributeID("replace");
      80         476 :     int at_filtered = xmb.GetAttributeID("filtered");
      81         476 :     int at_merge = xmb.GetAttributeID("merge");
      82         476 :     int at_op = xmb.GetAttributeID("op");
      83         476 :     int at_datatype = xmb.GetAttributeID("datatype");
      84         476 :     enum op {
      85             :         INVALID,
      86             :         ADD,
      87             :         MUL,
      88             :         MUL_ROUND
      89         476 :     } op = INVALID;
      90         476 :     bool replacing = false;
      91         476 :     bool filtering = false;
      92         476 :     bool merging = false;
      93         476 :     {
      94        1200 :         XERO_ITER_ATTR(element, attr)
      95             :         {
      96         135 :             if (attr.Name == at_disable)
      97             :             {
      98           2 :                 m_Childs.erase(name);
      99          22 :                 return;
     100             :             }
     101         133 :             else if (attr.Name == at_replace)
     102             :             {
     103           4 :                 m_Childs.erase(name);
     104           4 :                 replacing = true;
     105             :             }
     106         129 :             else if (attr.Name == at_filtered)
     107             :             {
     108             :                 filtering = true;
     109             :             }
     110         124 :             else if (attr.Name == at_merge)
     111             :             {
     112          60 :                 if (m_Childs.find(name) == m_Childs.end())
     113             :                     return;
     114             :                 merging = true;
     115             :             }
     116          94 :             else if (attr.Name == at_op)
     117             :             {
     118           3 :                 if (attr.Value == "add")
     119             :                     op = ADD;
     120           2 :                 else if (attr.Value == "mul")
     121             :                     op = MUL;
     122           1 :                 else if (attr.Value == "mul_round")
     123             :                     op = MUL_ROUND;
     124             :                 else
     125           0 :                     LOGWARNING("Invalid op '%ls'", attr.Value);
     126             :             }
     127             :         }
     128             :     }
     129         454 :     {
     130        1122 :         XERO_ITER_ATTR(element, attr)
     131             :         {
     132         112 :             if (attr.Name == at_datatype && attr.Value == "tokens")
     133             :             {
     134          10 :                 CParamNode& node = m_Childs[name];
     135             : 
     136             :                 // Split into tokens
     137          20 :                 std::vector<std::string> oldTokens;
     138          20 :                 std::vector<std::string> newTokens;
     139          10 :                 if (!replacing && !node.m_Value.empty()) // ignore the old tokens if replace="" was given
     140           2 :                     boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space(), boost::algorithm::token_compress_on);
     141          20 :                 if (!value.empty())
     142          10 :                     boost::algorithm::split(newTokens, value, boost::algorithm::is_space(), boost::algorithm::token_compress_on);
     143             : 
     144             :                 // Merge the two lists
     145          10 :                 std::vector<std::string> tokens = oldTokens;
     146          45 :                 for (size_t i = 0; i < newTokens.size(); ++i)
     147             :                 {
     148          75 :                     if (newTokens[i][0] == '-')
     149             :                     {
     150           3 :                         std::vector<std::string>::iterator tokenIt = std::find(tokens.begin(), tokens.end(), newTokens[i].substr(1));
     151           9 :                         if (tokenIt != tokens.end())
     152           3 :                             tokens.erase(tokenIt);
     153             :                         else
     154           5 :                             LOGWARNING("[ParamNode] Could not remove token '%s' from node '%s'%s; not present in list nor inherited (possible typo?)",
     155             :                                 newTokens[i].substr(1), name, sourceIdentifier ? (" in '" + utf8_from_wstring(sourceIdentifier) + "'").c_str() : "");
     156             :                     }
     157             :                     else
     158             :                     {
     159          88 :                         if (std::find(oldTokens.begin(), oldTokens.end(), newTokens[i]) == oldTokens.end())
     160          44 :                             tokens.push_back(newTokens[i]);
     161             :                     }
     162             :                 }
     163             : 
     164          10 :                 node.m_Value = boost::algorithm::join(tokens, " ");
     165          10 :                 hasSetValue = true;
     166          10 :                 break;
     167             :             }
     168             :         }
     169             :     }
     170             : 
     171             :     // Add this element as a child node
     172         454 :     CParamNode& node = m_Childs[name];
     173         454 :     if (op != INVALID)
     174             :     {
     175             :         // TODO: Support parsing of data types other than fixed; log warnings in other cases
     176           3 :         fixed oldval = node.ToFixed();
     177           3 :         fixed mod = fixed::FromString(value);
     178             : 
     179           3 :         switch (op)
     180             :         {
     181           1 :         case ADD:
     182           2 :             node.m_Value = (oldval + mod).ToString();
     183           1 :             break;
     184           1 :         case MUL:
     185           2 :             node.m_Value = oldval.Multiply(mod).ToString();
     186           1 :             break;
     187           1 :         case MUL_ROUND:
     188           4 :             node.m_Value = fixed::FromInt(oldval.Multiply(mod).ToInt_RoundToNearest()).ToString();
     189           1 :             break;
     190             :         default:
     191             :             break;
     192             :         }
     193           3 :         hasSetValue = true;
     194             :     }
     195             : 
     196         454 :     if (!hasSetValue && !merging)
     197         431 :         node.m_Value = value;
     198             : 
     199             :     // We also need to reset node's script val, even if it has no children
     200             :     // or if the attributes change.
     201         908 :     node.ResetScriptVal();
     202             : 
     203             :     // For the filtered case
     204         454 :     ChildrenMap childs;
     205             : 
     206             :     // Recurse through the element's children
     207        1672 :     XERO_ITER_EL(element, child)
     208             :     {
     209         382 :         node.ApplyLayer(xmb, child, sourceIdentifier);
     210         382 :         if (filtering)
     211             :         {
     212         107 :             std::string childname = xmb.GetElementString(child.GetNodeName());
     213          70 :             if (node.m_Childs.find(childname) != node.m_Childs.end())
     214          17 :                 childs[childname] = std::move(node.m_Childs[childname]);
     215             :         }
     216             :     }
     217             : 
     218         454 :     if (filtering)
     219           5 :         node.m_Childs.swap(childs);
     220             : 
     221             :     // Add the element's attributes, prefixing names with "@"
     222        1588 :     XERO_ITER_ATTR(element, attr)
     223             :     {
     224             :         // Skip special attributes
     225         113 :         if (attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered)
     226          22 :             continue;
     227             :         // Add any others
     228          91 :         const char* attrName(xmb.GetAttributeString(attr.Name));
     229          91 :         node.m_Childs[CStr("@") + attrName].m_Value = attr.Value;
     230             :     }
     231             : }
     232             : 
     233         353 : const CParamNode& CParamNode::GetChild(const char* name) const
     234             : {
     235         765 :     ChildrenMap::const_iterator it = m_Childs.find(name);
     236         706 :     if (it == m_Childs.end())
     237             :         return g_NullNode;
     238         448 :     return it->second;
     239             : }
     240             : 
     241         226 : bool CParamNode::IsOk() const
     242             : {
     243         226 :     return m_IsOk;
     244             : }
     245             : 
     246           0 : const std::wstring CParamNode::ToWString() const
     247             : {
     248           0 :     return wstring_from_utf8(m_Value);
     249             : }
     250             : 
     251          11 : const std::string& CParamNode::ToString() const
     252             : {
     253          11 :     return m_Value;
     254             : }
     255             : 
     256           0 : const CStrIntern CParamNode::ToUTF8Intern() const
     257             : {
     258           0 :     return CStrIntern(m_Value);
     259             : }
     260             : 
     261          24 : int CParamNode::ToInt() const
     262             : {
     263          48 :     return std::strtol(m_Value.c_str(), nullptr, 10);
     264             : }
     265             : 
     266          53 : fixed CParamNode::ToFixed() const
     267             : {
     268          53 :     return fixed::FromString(m_Value);
     269             : }
     270             : 
     271           0 : float CParamNode::ToFloat() const
     272             : {
     273           0 :     return std::strtof(m_Value.c_str(), nullptr);
     274             : }
     275             : 
     276           8 : bool CParamNode::ToBool() const
     277             : {
     278           8 :     if (m_Value == "true")
     279             :         return true;
     280             :     else
     281           6 :         return false;
     282             : }
     283             : 
     284          21 : const CParamNode::ChildrenMap& CParamNode::GetChildren() const
     285             : {
     286          21 :     return m_Childs;
     287             : }
     288             : 
     289          38 : std::string CParamNode::EscapeXMLString(const std::string& str)
     290             : {
     291          38 :     std::string ret;
     292          38 :     ret.reserve(str.size());
     293             :     // TODO: would be nice to check actual v1.0 XML codepoints,
     294             :     // but our UTF8 validation routines are lacking.
     295         187 :     for (size_t i = 0; i < str.size(); ++i)
     296             :     {
     297         149 :         char c = str[i];
     298         149 :         switch (c)
     299             :         {
     300           4 :         case '<': ret += "&lt;"; break;
     301           2 :         case '>': ret += "&gt;"; break;
     302           1 :         case '&': ret += "&amp;"; break;
     303           1 :         case '"': ret += "&quot;"; break;
     304           1 :         case '\t': ret += "&#9;"; break;
     305           1 :         case '\n': ret += "&#10;"; break;
     306           1 :         case '\r': ret += "&#13;"; break;
     307         138 :         default:
     308         138 :             ret += c;
     309             :         }
     310             :     }
     311          38 :     return ret;
     312             : }
     313             : 
     314          33 : std::string CParamNode::ToXMLString() const
     315             : {
     316          33 :     std::stringstream strm;
     317          33 :     ToXMLString(strm);
     318          33 :     return strm.str();
     319             : }
     320             : 
     321         205 : void CParamNode::ToXMLString(std::ostream& strm) const
     322             : {
     323         205 :     strm << m_Value;
     324             : 
     325         410 :     ChildrenMap::const_iterator it = m_Childs.begin();
     326         617 :     for (; it != m_Childs.end(); ++it)
     327             :     {
     328             :         // Skip attributes here (they were handled when the caller output the tag)
     329         414 :         if (it->first.length() && it->first[0] == '@')
     330             :             continue;
     331             : 
     332         516 :         strm << "<" << it->first;
     333             : 
     334             :         // Output the child's attributes first
     335         516 :         ChildrenMap::const_iterator cit = it->second.m_Childs.begin();
     336         687 :         for (; cit != it->second.m_Childs.end(); ++cit)
     337             :         {
     338         342 :             if (cit->first.length() && cit->first[0] == '@')
     339             :             {
     340         272 :                 std::string attrname (cit->first.begin()+1, cit->first.end());
     341         136 :                 strm << " " << attrname << "=\"" << EscapeXMLString(cit->second.m_Value) << "\"";
     342             :             }
     343             :         }
     344             : 
     345         172 :         strm << ">";
     346             : 
     347         344 :         it->second.ToXMLString(strm);
     348             : 
     349         516 :         strm << "</" << it->first << ">";
     350             :     }
     351         205 : }
     352             : 
     353          57 : void CParamNode::ToJSVal(const ScriptRequest& rq, bool cacheValue, JS::MutableHandleValue ret) const
     354             : {
     355          57 :     if (cacheValue && m_ScriptVal != NULL)
     356             :     {
     357          69 :         ret.set(*m_ScriptVal);
     358          23 :         return;
     359             :     }
     360             : 
     361          34 :     ConstructJSVal(rq, ret);
     362             : 
     363          34 :     if (cacheValue)
     364          34 :         m_ScriptVal.reset(new JS::PersistentRootedValue(rq.cx, ret));
     365             : }
     366             : 
     367          83 : void CParamNode::ConstructJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret) const
     368             : {
     369         166 :     if (m_Childs.empty())
     370             :     {
     371             :         // Empty node - map to undefined
     372         116 :         if (m_Value.empty())
     373             :         {
     374          16 :             ret.setUndefined();
     375          16 :             return;
     376             :         }
     377             : 
     378             :         // Just a string
     379         168 :         JS::RootedString str(rq.cx, JS_NewStringCopyUTF8Z(rq.cx, JS::ConstUTF8CharsZ(m_Value.data(), m_Value.size())));
     380          84 :         str.set(JS_AtomizeAndPinJSString(rq.cx, str));
     381          84 :         if (str)
     382             :         {
     383          84 :             ret.setString(str);
     384          42 :             return;
     385             :         }
     386             :         // TODO: report error
     387           0 :         ret.setUndefined();
     388           0 :         return;
     389             :     }
     390             : 
     391             :     // Got child nodes - convert this node into a hash-table-style object:
     392             : 
     393          50 :     JS::RootedObject obj(rq.cx, JS_NewPlainObject(rq.cx));
     394          50 :     if (!obj)
     395             :     {
     396           0 :         ret.setUndefined();
     397           0 :         return; // TODO: report error
     398             :     }
     399             : 
     400          50 :     JS::RootedValue childVal(rq.cx);
     401          99 :     for (std::map<std::string, CParamNode>::const_iterator it = m_Childs.begin(); it != m_Childs.end(); ++it)
     402             :     {
     403         147 :         it->second.ConstructJSVal(rq, &childVal);
     404         245 :         if (!JS_SetProperty(rq.cx, obj, it->first.c_str(), childVal))
     405             :         {
     406           0 :             ret.setUndefined();
     407           0 :             return; // TODO: report error
     408             :         }
     409             :     }
     410             : 
     411             :     // If the node has a string too, add that as an extra property
     412          50 :     if (!m_Value.empty())
     413             :     {
     414          20 :         std::u16string text(m_Value.begin(), m_Value.end());
     415          12 :         JS::RootedString str(rq.cx, JS_AtomizeAndPinUCStringN(rq.cx, text.c_str(), text.length()));
     416           8 :         if (!str)
     417             :         {
     418           0 :             ret.setUndefined();
     419           0 :             return; // TODO: report error
     420             :         }
     421             : 
     422          20 :         JS::RootedValue subChildVal(rq.cx, JS::StringValue(str));
     423          12 :         if (!JS_SetProperty(rq.cx, obj, "_string", subChildVal))
     424             :         {
     425           0 :             ret.setUndefined();
     426           0 :             return; // TODO: report error
     427             :         }
     428             :     }
     429             : 
     430          75 :     ret.setObject(*obj);
     431             : }
     432             : 
     433           0 : void CParamNode::ResetScriptVal()
     434             : {
     435        1860 :     m_ScriptVal = NULL;
     436           0 : }

Generated by: LCOV version 1.13