LCOV - code coverage report
Current view: top level - source/ps/XML - XMLWriter.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 117 165 70.9 %
Date: 2023-01-19 00:18:29 Functions: 22 32 68.8 %

          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 "XMLWriter.h"
      21             : 
      22             : #include "ps/CLogger.h"
      23             : #include "ps/Filesystem.h"
      24             : #include "ps/XML/Xeromyces.h"
      25             : #include "lib/utf8.h"
      26             : #include "lib/allocators/shared_ptr.h"
      27             : #include "lib/sysdep/cpu.h"
      28             : #include "maths/Fixed.h"
      29             : 
      30             : 
      31             : // TODO (maybe): Write to the file frequently, instead of buffering
      32             : // the entire file, so that large files get written faster.
      33             : 
      34             : namespace
      35             : {
      36           5 :     CStr escapeAttributeValue(const char* input)
      37             :     {
      38             :         // Spec says:
      39             :         //     AttValue ::= '"' ([^<&"] | Reference)* '"'
      40             :         // so > is allowed in attribute values, so we don't bother escaping it.
      41             : 
      42           5 :         CStr ret = input;
      43           5 :         ret.Replace("&", "&amp;");
      44           5 :         ret.Replace("<", "&lt;");
      45           5 :         ret.Replace("\"", "&quot;");
      46           5 :         return ret;
      47             :     }
      48             : 
      49          12 :     CStr escapeCharacterData(const char* input)
      50             :     {
      51             :         //     CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)
      52             : 
      53          12 :         CStr ret = input;
      54          12 :         ret.Replace("&", "&amp;");
      55          12 :         ret.Replace("<", "&lt;");
      56          12 :         ret.Replace("]]>", "]]&gt;");
      57          12 :         return ret;
      58             :     }
      59             : 
      60           1 :     CStr escapeCDATA(const char* input)
      61             :     {
      62           1 :         CStr ret = input;
      63           1 :         ret.Replace("]]>", "]]>]]&gt;<![CDATA[");
      64           1 :         return ret;
      65             :     }
      66             : 
      67           3 :     CStr escapeComment(const char* input)
      68             :     {
      69             :         //     Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
      70             :         // This just avoids double-hyphens, and doesn't enforce the no-hyphen-at-end
      71             :         // rule, since it's only used in contexts where there's already a space
      72             :         // between this data and the -->.
      73           3 :         CStr ret = input;
      74           3 :         ret.Replace("--", "\xE2\x80\x90\xE2\x80\x90");
      75             :             // replace with U+2010 HYPHEN, because it's close enough and it's
      76             :             // probably nicer than inserting spaces or deleting hyphens or
      77             :             // any alternative
      78           3 :         return ret;
      79             :     }
      80             : }
      81             : 
      82             : enum { EL_ATTR, EL_TEXT, EL_SUBEL };
      83             : 
      84           9 : XMLWriter_File::XMLWriter_File()
      85             :     : m_Indent(0), m_LastElement(NULL),
      86           9 :     m_PrettyPrint(true)
      87             : {
      88             :     // Encoding is always UTF-8 - that's one of the only two guaranteed to be
      89             :     // supported by XML parsers (along with UTF-16), and there's not much need
      90             :     // to let people choose another.
      91           9 :     m_Data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
      92           9 : }
      93             : 
      94           0 : bool XMLWriter_File::StoreVFS(const PIVFS& vfs, const VfsPath& pathname)
      95             : {
      96           0 :     if (m_LastElement) debug_warn(L"ERROR: Saving XML while an element is still open");
      97             : 
      98           0 :     const size_t size = m_Data.length();
      99           0 :     std::shared_ptr<u8> data;
     100           0 :     AllocateAligned(data, size, maxSectorSize);
     101           0 :     memcpy(data.get(), m_Data.data(), size);
     102           0 :     Status ret = vfs->CreateFile(pathname, data, size);
     103           0 :     if (ret < 0)
     104             :     {
     105           0 :         LOGERROR("Error saving XML data through VFS: %lld '%s'", (long long)ret, pathname.string8());
     106           0 :         return false;
     107             :     }
     108           0 :     return true;
     109             : }
     110             : 
     111           9 : const CStr& XMLWriter_File::GetOutput()
     112             : {
     113           9 :     return m_Data;
     114             : }
     115             : 
     116             : 
     117           0 : void XMLWriter_File::XMB(const XMBData& xmb)
     118             : {
     119           0 :     ElementXMB(xmb, xmb.GetRoot());
     120           0 : }
     121             : 
     122           0 : void XMLWriter_File::ElementXMB(const XMBData& xmb, XMBElement el)
     123             : {
     124           0 :     XMLWriter_Element writer(*this, xmb.GetElementString(el.GetNodeName()));
     125             : 
     126           0 :     XERO_ITER_ATTR(el, attr)
     127           0 :         writer.Attribute(xmb.GetAttributeString(attr.Name), attr.Value);
     128             : 
     129           0 :     XERO_ITER_EL(el, child)
     130           0 :         ElementXMB(xmb, child);
     131           0 : }
     132             : 
     133           3 : void XMLWriter_File::Comment(const char* text)
     134             : {
     135           3 :     ElementStart(NULL, "!-- ");
     136           3 :     m_Data += escapeComment(text);
     137           3 :     m_Data += " -->";
     138           3 :     --m_Indent;
     139           3 : }
     140             : 
     141          27 : CStr XMLWriter_File::Indent()
     142             : {
     143          27 :     return std::string(m_Indent, '\t');
     144             : }
     145             : 
     146          24 : void XMLWriter_File::ElementStart(XMLWriter_Element* element, const char* name)
     147             : {
     148          24 :     if (m_LastElement) m_LastElement->Close(EL_SUBEL);
     149          24 :     m_LastElement = element;
     150             : 
     151          24 :     if (m_PrettyPrint)
     152             :     {
     153          21 :         m_Data += "\n";
     154          21 :         m_Data += Indent();
     155             :     }
     156          24 :     m_Data += "<";
     157          24 :     m_Data += name;
     158             : 
     159          24 :     ++m_Indent;
     160          24 : }
     161             : 
     162          18 : void XMLWriter_File::ElementClose()
     163             : {
     164          18 :     m_Data += ">";
     165          18 : }
     166             : 
     167          21 : void XMLWriter_File::ElementEnd(const char* name, int type)
     168             : {
     169          21 :     --m_Indent;
     170          21 :     m_LastElement = NULL;
     171             : 
     172          21 :     switch (type)
     173             :     {
     174           2 :     case EL_ATTR:
     175           2 :         m_Data += "/>";
     176           2 :         break;
     177          11 :     case EL_TEXT:
     178          11 :         m_Data += "</";
     179          11 :         m_Data += name;
     180          11 :         m_Data += ">";
     181          11 :         break;
     182           8 :     case EL_SUBEL:
     183           8 :         if (m_PrettyPrint)
     184             :         {
     185           6 :             m_Data += "\n";
     186           6 :             m_Data += Indent();
     187             :         }
     188           8 :         m_Data += "</";
     189           8 :         m_Data += name;
     190           8 :         m_Data += ">";
     191           8 :         break;
     192           0 :     default:
     193           0 :         DEBUG_WARN_ERR(ERR::LOGIC);
     194             :     }
     195          21 : }
     196             : 
     197          13 : void XMLWriter_File::ElementText(const char* text, bool cdata)
     198             : {
     199          13 :     if (cdata)
     200             :     {
     201           1 :         m_Data += "<![CDATA[";
     202           1 :         m_Data += escapeCDATA(text);
     203           1 :         m_Data += "]]>";
     204             :     }
     205             :     else
     206             :     {
     207          12 :         m_Data += escapeCharacterData(text);
     208             :     }
     209          13 : }
     210             : 
     211             : 
     212          20 : XMLWriter_Element::XMLWriter_Element(XMLWriter_File& file, const char* name)
     213          20 :     : m_File(&file), m_Name(name), m_Type(EL_ATTR)
     214             : {
     215          20 :     m_File->ElementStart(this, name);
     216          20 : }
     217             : 
     218             : 
     219          40 : XMLWriter_Element::~XMLWriter_Element()
     220             : {
     221          20 :     m_File->ElementEnd(m_Name.c_str(), m_Type);
     222          20 : }
     223             : 
     224             : 
     225          20 : void XMLWriter_Element::Close(int type)
     226             : {
     227          20 :     if (m_Type == type)
     228           2 :         return;
     229             : 
     230          18 :     m_File->ElementClose();
     231          18 :     m_Type = type;
     232             : }
     233             : 
     234             : 
     235             : // Template specialisations for various string types:
     236             : 
     237          12 : template <> void XMLWriter_Element::Text<const char*>(const char* text, bool cdata)
     238             : {
     239          12 :     Close(EL_TEXT);
     240          12 :     m_File->ElementText(text, cdata);
     241          12 : }
     242             : 
     243           1 : template <> void XMLWriter_Element::Text<const wchar_t*>(const wchar_t* text, bool cdata)
     244             : {
     245           1 :     Text( CStrW(text).ToUTF8().c_str(), cdata );
     246           1 : }
     247             : 
     248             : //
     249             : 
     250           6 : template <> void XMLWriter_File::ElementAttribute<const char*>(const char* name, const char* const& value, bool newelement)
     251             : {
     252           6 :     if (newelement)
     253             :     {
     254           1 :         ElementStart(NULL, name);
     255           1 :         m_Data += ">";
     256           1 :         ElementText(value, false);
     257           1 :         ElementEnd(name, EL_TEXT);
     258             :     }
     259             :     else
     260             :     {
     261           5 :         ENSURE(m_LastElement && m_LastElement->m_Type == EL_ATTR);
     262           5 :         m_Data += " ";
     263           5 :         m_Data += name;
     264           5 :         m_Data += "=\"";
     265           5 :         m_Data += escapeAttributeValue(value);
     266           5 :         m_Data += "\"";
     267             :     }
     268           6 : }
     269             : 
     270             : // Attribute/setting value-to-string template specialisations.
     271             : //
     272             : // These only deal with basic types. Anything more complicated should
     273             : // be converted into a basic type by whatever is making use of XMLWriter,
     274             : // to keep game-related logic out of the not-directly-game-related code here.
     275             : 
     276           0 : template <> void XMLWriter_File::ElementAttribute<CStr>(const char* name, const CStr& value, bool newelement)
     277             : {
     278           0 :     ElementAttribute(name, value.c_str(), newelement);
     279           0 : }
     280           0 : template <> void XMLWriter_File::ElementAttribute<std::string>(const char* name, const std::string& value, bool newelement)
     281             : {
     282           0 :     ElementAttribute(name, value.c_str(), newelement);
     283           0 : }
     284             : // Encode Unicode strings as UTF-8
     285           0 : template <> void XMLWriter_File::ElementAttribute<CStrW>(const char* name, const CStrW& value, bool newelement)
     286             : {
     287           0 :     ElementAttribute(name, value.ToUTF8(), newelement);
     288           0 : }
     289           0 : template <> void XMLWriter_File::ElementAttribute<std::wstring>(const char* name, const std::wstring& value, bool newelement)
     290             : {
     291           0 :     ElementAttribute(name, utf8_from_wstring(value).c_str(), newelement);
     292           0 : }
     293             : 
     294           0 : template <> void XMLWriter_File::ElementAttribute<fixed>(const char* name, const fixed& value, bool newelement)
     295             : {
     296           0 :     ElementAttribute(name, value.ToString().c_str(), newelement);
     297           0 : }
     298             : 
     299           1 : template <> void XMLWriter_File::ElementAttribute<int>(const char* name, const int& value, bool newelement)
     300             : {
     301           2 :     std::stringstream ss;
     302           1 :     ss << value;
     303           1 :     ElementAttribute(name, ss.str().c_str(), newelement);
     304           1 : }
     305             : 
     306           0 : template <> void XMLWriter_File::ElementAttribute<unsigned int>(const char* name, const unsigned int& value, bool newelement)
     307             : {
     308           0 :     std::stringstream ss;
     309           0 :     ss << value;
     310           0 :     ElementAttribute(name, ss.str().c_str(), newelement);
     311           0 : }
     312             : 
     313           0 : template <> void XMLWriter_File::ElementAttribute<float>(const char* name, const float& value, bool newelement)
     314             : {
     315           0 :     std::stringstream ss;
     316           0 :     ss << value;
     317           0 :     ElementAttribute(name, ss.str().c_str(), newelement);
     318           0 : }
     319             : 
     320           2 : template <> void XMLWriter_File::ElementAttribute<double>(const char* name, const double& value, bool newelement)
     321             : {
     322           4 :     std::stringstream ss;
     323           2 :     ss << value;
     324           2 :     ElementAttribute(name, ss.str().c_str(), newelement);
     325           5 : }

Generated by: LCOV version 1.13