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: 201 225 89.3 %
Date: 2023-01-19 00:18:29 Functions: 21 25 84.0 %

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

Generated by: LCOV version 1.13