You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Legacy/CrySystem/XML/xml.cpp

1846 lines
48 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "CrySystem_precompiled.h"
#include <stdlib.h>
#define XML_STATIC // Alternative to defining this here would be setting it project-wide
#include <expat.h>
#include "xml.h"
#include <algorithm>
#include <stdio.h>
#include <AzFramework/Archive/IArchive.h>
#include <CryCommon/Cry_Color.h>
#include "XMLBinaryReader.h"
#define FLOAT_FMT "%.8g"
#define DOUBLE_FMT "%.17g"
#include "../SimpleStringPool.h"
#include "System.h"
#if AZ_TRAIT_OS_PLATFORM_APPLE || defined(AZ_PLATFORM_LINUX)
#include <clocale>
#include <locale>
class LocaleResetter
{
public:
LocaleResetter()
{
m_oldLocale = std::setlocale(LC_NUMERIC, "C");
}
~LocaleResetter()
{
std::setlocale(LC_NUMERIC, m_oldLocale);
}
private:
char* m_oldLocale;
};
#define SCOPED_LOCALE_RESETTER LocaleResetter l
#else
// noop on Windows
#define SCOPED_LOCALE_RESETTER
#endif
// Global counter for memory allocated in XML string pools.
size_t CSimpleStringPool::g_nTotalAllocInXmlStringPools = 0;
//////////////////////////////////////////////////////////////////////////
static int __cdecl ascii_stricmp(const char* dst, const char* src)
{
int f, l;
do
{
if (((f = (unsigned char)(*(dst++))) >= 'A') && (f <= 'Z'))
{
f -= 'A' - 'a';
}
if (((l = (unsigned char)(*(src++))) >= 'A') && (l <= 'Z'))
{
l -= 'A' - 'a';
}
}
while (f && (f == l));
return(f - l);
}
//////////////////////////////////////////////////////////////////////////
XmlStrCmpFunc g_pXmlStrCmp = &ascii_stricmp;
//////////////////////////////////////////////////////////////////////////
class CXmlStringData
: public IXmlStringData
{
public:
int m_nRefCount;
XmlString m_string;
CXmlStringData() { m_nRefCount = 0; }
virtual void AddRef() { ++m_nRefCount; }
virtual void Release()
{
if (--m_nRefCount <= 0)
{
delete this;
}
}
virtual const char* GetString() { return m_string.c_str(); };
virtual size_t GetStringLength() { return m_string.size(); };
};
//////////////////////////////////////////////////////////////////////////
class CXmlStringPool
: public IXmlStringPool
{
public:
explicit CXmlStringPool(bool bReuseStrings)
: m_stringPool(bReuseStrings) {}
const char* AddString(const char* str) { return m_stringPool.Append(str, (int)strlen(str)); }
void Clear() { m_stringPool.Clear(); }
void SetBlockSize(unsigned int nBlockSize) { m_stringPool.SetBlockSize(nBlockSize); }
private:
CSimpleStringPool m_stringPool;
};
//xml_node_allocator XmlDynArrayAlloc::allocator;
//size_t XmlDynArrayAlloc::m_iAllocated = 0;
/**
******************************************************************************
* CXmlNode implementation.
******************************************************************************
*/
void CXmlNode::DeleteThis()
{
delete this;
}
CXmlNode::~CXmlNode()
{
m_nRefCount = 1; // removeAllChildsImpl can make an XmlNodeRef to this node whilst it is deleting the children
// doing this will cause the ref count to be increment and decremented and cause delete to be called again,
// leading to a recursion crash. upping the ref count here once destruction has started avoids this problem
removeAllChildsImpl();
SAFE_DELETE(m_pAttributes);
m_pStringPool->Release();
}
CXmlNode::CXmlNode()
: m_pStringPool(NULL) // must be changed later.
, m_tag("")
, m_content("")
, m_parent(NULL)
, m_pChilds(NULL)
, m_pAttributes(NULL)
, m_line(0)
, m_isProcessingInstruction(false)
{
m_nRefCount = 0; //TODO: move initialization to IXmlNode constructor
}
CXmlNode::CXmlNode(const char* tag, bool bReuseStrings, bool bIsProcessingInstruction)
: m_content("")
, m_parent(NULL)
, m_pChilds(NULL)
, m_pAttributes(NULL)
, m_line(0)
, m_isProcessingInstruction(bIsProcessingInstruction)
{
m_nRefCount = 0; //TODO: move initialization to IXmlNode constructor
m_pStringPool = new CXmlStringPool(bReuseStrings);
m_pStringPool->AddRef();
m_tag = m_pStringPool->AddString(tag);
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef CXmlNode::createNode(const char* tag)
{
CXmlNode* pNewNode = new CXmlNode;
pNewNode->m_pStringPool = m_pStringPool;
m_pStringPool->AddRef();
pNewNode->m_tag = m_pStringPool->AddString(tag);
return XmlNodeRef(pNewNode);
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::setTag(const char* tag)
{
m_tag = m_pStringPool->AddString(tag);
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::setContent(const char* str)
{
m_content = m_pStringPool->AddString(str);
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::isTag(const char* tag) const
{
return g_pXmlStrCmp(tag, m_tag) == 0;
}
const char* CXmlNode::getAttr(const char* key) const
{
const char* svalue = GetValue(key);
if (svalue)
{
return svalue;
}
return "";
}
bool CXmlNode::getAttr(const char* key, const char** value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
*value = svalue;
return true;
}
else
{
*value = "";
return false;
}
}
bool CXmlNode::haveAttr(const char* key) const
{
if (m_pAttributes)
{
XmlAttrConstIter it = GetAttrConstIterator(key);
if (it != m_pAttributes->end())
{
return true;
}
}
return false;
}
void CXmlNode::delAttr(const char* key)
{
if (m_pAttributes)
{
XmlAttrIter it = GetAttrIterator(key);
if (it != m_pAttributes->end())
{
m_pAttributes->erase(it);
}
}
}
void CXmlNode::removeAllAttributes()
{
if (m_pAttributes)
{
m_pAttributes->clear();
SAFE_DELETE(m_pAttributes);
}
}
void CXmlNode::setAttr(const char* key, const char* value)
{
if (!m_pAttributes)
{
m_pAttributes = new XmlAttributes;
}
assert(m_pAttributes);
XmlAttrIter it = GetAttrIterator(key);
if (it == m_pAttributes->end())
{
XmlAttribute tempAttr;
tempAttr.key = m_pStringPool->AddString(key);
tempAttr.value = m_pStringPool->AddString(value);
m_pAttributes->push_back(tempAttr);
// Sort attributes.
//std::sort( m_pAttributes->begin(),m_pAttributes->end() );
}
else
{
// If already exist, override this member.
it->value = m_pStringPool->AddString(value);
}
}
void CXmlNode::setAttr(const char* key, int value)
{
char str[128];
azitoa(value, str, AZ_ARRAY_SIZE(str), 10);
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, unsigned int value)
{
char str[128];
azui64toa(value, str, AZ_ARRAY_SIZE(str), 10);
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, float value)
{
char str[128];
SCOPED_LOCALE_RESETTER;
sprintf_s(str, FLOAT_FMT, value);
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, double value)
{
char str[128];
SCOPED_LOCALE_RESETTER;
sprintf_s(str, DOUBLE_FMT, value);
setAttr(key, str);
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::setAttr(const char* key, int64 value)
{
char str[32];
sprintf_s(str, "%" PRId64, value);
setAttr(key, str);
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::setAttr(const char* key, uint64 value, bool useHexFormat)
{
char str[32];
if (useHexFormat)
{
sprintf_s(str, "%" PRIX64, value);
}
else
{
sprintf_s(str, "%" PRIu64, value);
}
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, const Ang3& value)
{
char str[128];
SCOPED_LOCALE_RESETTER;
sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z);
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, const Vec3& value)
{
char str[128];
SCOPED_LOCALE_RESETTER;
sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z);
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, const Vec4& value)
{
char str[128];
SCOPED_LOCALE_RESETTER;
sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.x, value.y, value.z, value.w);
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, const Vec2& value)
{
char str[128];
SCOPED_LOCALE_RESETTER;
sprintf_s(str, FLOAT_FMT "," FLOAT_FMT, value.x, value.y);
setAttr(key, str);
}
void CXmlNode::setAttr(const char* key, const Quat& value)
{
char str[128];
SCOPED_LOCALE_RESETTER;
sprintf_s(str, FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT "," FLOAT_FMT, value.w, value.v.x, value.v.y, value.v.z);
setAttr(key, str);
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, int& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
value = atoi(svalue);
return true;
}
return false;
}
bool CXmlNode::getAttr(const char* key, unsigned int& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
value = strtoul(svalue, NULL, 10);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, int64& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
value = strtoll(key, nullptr, 10);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, uint64& value, bool useHexFormat) const
{
const char* svalue = GetValue(key);
if (svalue)
{
value = strtoull(key, nullptr, useHexFormat ? 16 : 10);
return true;
}
return false;
}
bool CXmlNode::getAttr(const char* key, bool& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
if (azstricmp(svalue, "true") == 0)
{
value = true;
}
else if (azstricmp(svalue, "false") == 0)
{
value = false;
}
else
{
value = atoi(svalue) != 0;
}
return true;
}
return false;
}
bool CXmlNode::getAttr(const char* key, float& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
value = (float)atof(svalue);
return true;
}
return false;
}
bool CXmlNode::getAttr(const char* key, double& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
value = atof(svalue);
return true;
}
return false;
}
bool CXmlNode::getAttr(const char* key, Ang3& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
SCOPED_LOCALE_RESETTER;
float x, y, z;
if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
{
value(x, y, z);
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, Vec3& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
SCOPED_LOCALE_RESETTER;
float x, y, z;
if (azsscanf(svalue, "%f,%f,%f", &x, &y, &z) == 3)
{
value = Vec3(x, y, z);
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, Vec4& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
SCOPED_LOCALE_RESETTER;
float x, y, z, w;
if (azsscanf(svalue, "%f,%f,%f,%f", &x, &y, &z, &w) == 4)
{
value = Vec4(x, y, z, w);
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, Vec2& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
SCOPED_LOCALE_RESETTER;
float x, y;
if (azsscanf(svalue, "%f,%f", &x, &y) == 2)
{
value = Vec2(x, y);
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, Quat& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
SCOPED_LOCALE_RESETTER;
float w, x, y, z;
if (azsscanf(svalue, "%f,%f,%f,%f", &w, &x, &y, &z) == 4)
{
if (fabs(w) > VEC_EPSILON || fabs(x) > VEC_EPSILON || fabs(y) > VEC_EPSILON || fabs(z) > VEC_EPSILON)
{
//[AlexMcC|02.03.10] directly assign to members to avoid triggering the assert in Quat() with data from bad assets
value.w = w;
value.v = Vec3(x, y, z);
return value.IsValid();
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttr(const char* key, ColorB& value) const
{
const char* svalue = GetValue(key);
if (svalue)
{
unsigned int r, g, b, a = 255;
int numFound = azsscanf(svalue, "%u,%u,%u,%u", &r, &g, &b, &a);
if (numFound == 3 || numFound == 4)
{
// If we only found 3 values, a should be unchanged, and still be 255
if (r < 256 && g < 256 && b < 256 && a < 256)
{
value = ColorB(static_cast<uint8>(r), static_cast<uint8>(g), static_cast<uint8>(b), static_cast<uint8>(a));
return true;
}
}
}
return false;
}
XmlNodeRef CXmlNode::findChild(const char* tag) const
{
if (m_pChilds)
{
XmlNodes& childs = *m_pChilds;
for (int i = 0, num = static_cast<int>(childs.size()); i < num; ++i)
{
if (childs[i]->isTag(tag))
{
return childs[i];
}
}
}
return 0;
}
void CXmlNode::removeChild(const XmlNodeRef& node)
{
if (m_pChilds)
{
XmlNodes::iterator it = std::find(m_pChilds->begin(), m_pChilds->end(), (IXmlNode*)node);
if (it != m_pChilds->end())
{
ReleaseChild(*it);
m_pChilds->erase(it);
}
}
}
void CXmlNode::removeAllChilds()
{
removeAllChildsImpl();
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::deleteChild(const char* tag)
{
if (m_pChilds)
{
XmlNodes& childs = *m_pChilds;
for (int i = 0, num = static_cast<int>(childs.size()); i < num; ++i)
{
if (childs[i]->isTag(tag))
{
ReleaseChild(childs[i]);
childs.erase(childs.begin() + i);
return;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::deleteChildAt(int nIndex)
{
if (m_pChilds)
{
XmlNodes& childs = *m_pChilds;
if (nIndex >= 0 && nIndex < (int)childs.size())
{
ReleaseChild(childs[nIndex]);
childs.erase(childs.begin() + nIndex);
}
}
}
//! Adds new child node.
void CXmlNode::addChild(const XmlNodeRef& node)
{
if (!m_pChilds)
{
m_pChilds = new XmlNodes;
}
assert(node != 0);
IXmlNode* pNode = ((IXmlNode*)node);
pNode->AddRef();
m_pChilds->push_back(pNode);
pNode->setParent(this);
};
void CXmlNode::shareChildren(const XmlNodeRef& inFromMe)
{
int numChildren = inFromMe->getChildCount();
removeAllChilds();
if (numChildren > 0)
{
XmlNodeRef child;
m_pChilds = new XmlNodes;
m_pChilds->reserve(numChildren);
for (int i = 0; i < numChildren; i++)
{
child = inFromMe->getChild(i);
child->AddRef();
// not overwriting parent assignment of child, we share the node but do not exclusively own it
m_pChilds->push_back(child);
}
}
}
void CXmlNode::setParent(const XmlNodeRef& inNewParent)
{
// note, parent ptrs are not ref counted
m_parent = inNewParent;
}
void CXmlNode::insertChild(int inIndex, const XmlNodeRef& inNewChild)
{
assert(inIndex >= 0 && inIndex <= getChildCount());
assert(inNewChild != 0);
if (inIndex >= 0 && inIndex <= getChildCount() && inNewChild)
{
if (getChildCount() == 0)
{
addChild(inNewChild);
}
else
{
IXmlNode* pNode = ((IXmlNode*)inNewChild);
pNode->AddRef();
m_pChilds->insert(m_pChilds->begin() + inIndex, pNode);
pNode->setParent(this);
}
}
}
void CXmlNode::replaceChild(int inIndex, const XmlNodeRef& inNewChild)
{
assert(inIndex >= 0 && inIndex < getChildCount());
assert(inNewChild != 0);
if (inIndex >= 0 && inIndex < getChildCount() && inNewChild)
{
IXmlNode* wasChild = (*m_pChilds)[inIndex];
if (wasChild->getParent() == this)
{
wasChild->setParent(XmlNodeRef()); // child is orphaned, will be freed by Release() below if this parent is last holding a reference to it
}
wasChild->Release();
inNewChild->AddRef();
(*m_pChilds)[inIndex] = inNewChild;
inNewChild->setParent(this);
}
}
XmlNodeRef CXmlNode::newChild(const char* tagName)
{
XmlNodeRef node = createNode(tagName);
addChild(node);
return node;
}
//! Get XML Node child nodes.
XmlNodeRef CXmlNode::getChild(int i) const
{
assert(m_pChilds);
XmlNodes& childs = *m_pChilds;
assert(i >= 0 && i < (int)childs.size());
return childs[i];
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::copyAttributes(XmlNodeRef fromNode)
{
IXmlNode* inode = fromNode;
CXmlNode* n = (CXmlNode*)inode;
assert(n);
PREFAST_ASSUME(n);
if (n != this)
{
if (n->m_pAttributes)
{
if (!m_pAttributes)
{
m_pAttributes = new XmlAttributes;
}
if (n->m_pStringPool == m_pStringPool)
{
*m_pAttributes = *(n->m_pAttributes);
}
else
{
XmlAttributes& lhs = *m_pAttributes;
const XmlAttributes& rhs = *(n->m_pAttributes);
lhs.resize(rhs.size());
for (size_t i = 0; i < rhs.size(); ++i)
{
lhs[i].key = m_pStringPool->AddString(rhs[i].key);
lhs[i].value = m_pStringPool->AddString(rhs[i].value);
}
}
}
else
{
SAFE_DELETE(m_pAttributes);
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttributeByIndex(int index, const char** key, const char** value)
{
if (m_pAttributes)
{
XmlAttributes::iterator it = m_pAttributes->begin();
if (it != m_pAttributes->end())
{
std::advance(it, index);
if (it != m_pAttributes->end())
{
*key = it->key;
*value = it->value;
return true;
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::getAttributeByIndex(int index, XmlString& key, XmlString& value)
{
if (m_pAttributes)
{
XmlAttributes::iterator it = m_pAttributes->begin();
if (it != m_pAttributes->end())
{
std::advance(it, index);
if (it != m_pAttributes->end())
{
key = it->key;
value = it->value;
return true;
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef CXmlNode::clone()
{
CXmlNode* node = new CXmlNode;
XmlNodeRef result(node);
node->m_pStringPool = m_pStringPool;
m_pStringPool->AddRef();
node->m_tag = m_tag;
node->m_content = m_content;
// Clone attributes.
CXmlNode* n = (CXmlNode*)(IXmlNode*)node;
n->copyAttributes(this);
// Clone sub nodes.
if (m_pChilds)
{
const XmlNodes& childs = *m_pChilds;
node->m_pChilds = new XmlNodes;
node->m_pChilds->reserve(childs.size());
for (int i = 0, num = static_cast<int>(childs.size()); i < num; ++i)
{
node->addChild(childs[i]->clone());
}
}
return result;
}
//////////////////////////////////////////////////////////////////////////
static void AddTabsToString(XmlString& xml, int level)
{
static const char* tabs[] = {
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
};
// Add tabs.
if (level < sizeof(tabs) / sizeof(tabs[0]))
{
xml += tabs[level];
}
else
{
for (int i = 0; i < level; i++)
{
xml += " ";
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CXmlNode::IsValidXmlString(const char* str) const
{
int len = static_cast<int>(strlen(str));
{
// Prevents invalid characters not from standard ASCII set to propagate to xml.
// A bit of hack for efficiency, fixes input string in place.
char* s = const_cast<char*>(str);
for (int i = 0; i < len; i++)
{
if ((unsigned char)s[i] > 0x7F)
{
s[i] = ' ';
}
}
}
if (strcspn(str, "\"\'&><") == len)
{
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
XmlString CXmlNode::MakeValidXmlString(const XmlString& instr) const
{
XmlString str = instr;
// check if str contains any invalid characters
AZ::StringFunc::Replace(str, "&", "&amp;");
AZ::StringFunc::Replace(str, "\"", "&quot;");
AZ::StringFunc::Replace(str, "\'", "&apos;");
AZ::StringFunc::Replace(str, "<", "&lt;");
AZ::StringFunc::Replace(str, ">", "&gt;");
AZ::StringFunc::Replace(str, "...", "&gt;");
AZ::StringFunc::Replace(str, "\n", "&#10;");
return str;
}
void CXmlNode::ReleaseChild(IXmlNode* pChild)
{
if (pChild)
{
if (pChild->getParent() == this) // if check to handle shared children which are supported by the CXmlNode impl
{
pChild->setParent(NULL);
}
pChild->Release();
}
}
void CXmlNode::removeAllChildsImpl()
{
if (m_pChilds)
{
for (XmlNodes::iterator iter = m_pChilds->begin(), endIter = m_pChilds->end(); iter != endIter; ++iter)
{
ReleaseChild(*iter);
}
SAFE_DELETE(m_pChilds);
}
}
//////////////////////////////////////////////////////////////////////////
void CXmlNode::AddToXmlString(XmlString& xml, int level, AZ::IO::HandleType fileHandle, size_t chunkSize) const
{
if (fileHandle != AZ::IO::InvalidHandle && chunkSize > 0)
{
size_t len = xml.length();
if (len >= chunkSize)
{
gEnv->pCryPak->FWrite(xml.c_str(), len, 1, fileHandle);
xml.assign (""); // should not free memory and does not!
}
}
AddTabsToString(xml, level);
const bool bHasChildren = (m_pChilds && !m_pChilds->empty());
// Begin Tag
if (!m_pAttributes || m_pAttributes->empty())
{
xml += "<";
if (m_isProcessingInstruction)
{
xml += "?";
}
xml += m_tag;
if (*m_content == 0 && !bHasChildren)
{
if (m_isProcessingInstruction)
{
xml += "?>\n";
}
else
{
// Compact tag form.
xml += " />\n";
}
return;
}
xml += ">";
}
else
{
xml += "<";
if (m_isProcessingInstruction)
{
xml += "?";
}
xml += m_tag;
xml += " ";
// Put attributes.
for (XmlAttributes::const_iterator it = m_pAttributes->begin(); it != m_pAttributes->end(); )
{
xml += it->key;
xml += "=\"";
if (IsValidXmlString(it->value))
{
xml += it->value;
}
else
{
xml += MakeValidXmlString(it->value);
}
++it;
if (it != m_pAttributes->end())
{
xml += "\" ";
}
else
{
xml += "\"";
}
}
if (*m_content == 0 && !bHasChildren)
{
if (m_isProcessingInstruction)
{
xml += "?>\n";
}
else
{
// Compact tag form.
xml += "/>\n";
}
return;
}
xml += ">";
}
// Put node content.
if (IsValidXmlString(m_content))
{
xml += m_content;
}
else
{
xml += MakeValidXmlString(m_content);
}
if (!bHasChildren)
{
xml += "</";
xml += m_tag;
xml += ">\n";
return;
}
xml += "\n";
// Add sub nodes.
for (XmlNodes::iterator it = m_pChilds->begin(), itEnd = m_pChilds->end(); it != itEnd; ++it)
{
IXmlNode* node = *it;
((CXmlNode*)node)->AddToXmlString(xml, level + 1, fileHandle, chunkSize);
}
// Add tabs.
AddTabsToString(xml, level);
xml += "</";
xml += m_tag;
xml += ">\n";
}
#if !defined(APPLE) && !defined(LINUX) && !defined(AZ_LEGACY_CRYSYSTEM_TRAIT_HASSTPCPY)
ILINE static char* stpcpy(char* dst, const char* src)
{
while (src[0])
{
dst[0] = src[0];
dst++;
src++;
}
return dst;
}
#endif
char* CXmlNode::AddToXmlStringUnsafe(char* xml, int level, char* endPtr, AZ::IO::HandleType fileHandle, size_t chunkSize) const
{
const bool bHasChildren = (m_pChilds && !m_pChilds->empty());
for (int i = 0; i < level; i++)
{
*(xml++) = ' ';
*(xml++) = ' ';
}
// Begin Tag
if (!m_pAttributes || m_pAttributes->empty())
{
*(xml++) = '<';
xml = stpcpy(xml, m_tag);
if (*m_content == 0 && !bHasChildren)
{
*(xml++) = '/';
*(xml++) = '>';
*(xml++) = '\n';
return xml;
}
*(xml++) = '>';
}
else
{
*(xml++) = '<';
xml = stpcpy(xml, m_tag);
*(xml++) = ' ';
// Put attributes.
for (XmlAttributes::const_iterator it = m_pAttributes->begin(); it != m_pAttributes->end(); )
{
xml = stpcpy(xml, it->key);
*(xml++) = '=';
*(xml++) = '\"';
#ifndef _RELEASE
if (it->value[strcspn(it->value, "\"\'&><")])
{
__debugbreak();
}
#endif
xml = stpcpy(xml, it->value);
++it;
*(xml++) = '\"';
if (it != m_pAttributes->end())
{
*(xml++) = ' ';
}
}
if (*m_content == 0 && !bHasChildren)
{
// Compact tag form.
*(xml++) = '/';
*(xml++) = '>';
*(xml++) = '\n';
return xml;
}
*(xml++) = '>';
}
#ifndef _RELEASE
if (m_content[strcspn(m_content, "\"\'&><")])
{
__debugbreak();
}
#endif
xml = stpcpy(xml, m_content);
if (!bHasChildren)
{
*(xml++) = '<';
*(xml++) = '/';
xml = stpcpy(xml, m_tag);
*(xml++) = '>';
*(xml++) = '\n';
return xml;
}
*(xml++) = '\n';
// Add sub nodes.
for (XmlNodes::iterator it = m_pChilds->begin(), itEnd = m_pChilds->end(); it != itEnd; ++it)
{
IXmlNode* node = *it;
xml = ((CXmlNode*)node)->AddToXmlStringUnsafe(xml, level + 1, endPtr, fileHandle, chunkSize);
}
for (int i = 0; i < level; i++)
{
*(xml++) = ' ';
*(xml++) = ' ';
}
*(xml++) = '<';
*(xml++) = '/';
xml = stpcpy(xml, m_tag);
*(xml++) = '>';
*(xml++) = '\n';
assert(xml < endPtr);
return xml;
}
//////////////////////////////////////////////////////////////////////////
IXmlStringData* CXmlNode::getXMLData(int nReserveMem) const
{
CXmlStringData* pStrData = new CXmlStringData;
pStrData->m_string.reserve(nReserveMem);
AddToXmlString(pStrData->m_string, 0);
return pStrData;
}
//////////////////////////////////////////////////////////////////////////
XmlString CXmlNode::getXML(int level) const
{
XmlString xml;
xml = "";
xml.reserve(1024);
AddToXmlString(xml, level);
return xml;
}
XmlString CXmlNode::getXMLUnsafe(int level, char* tmpBuffer, uint32 sizeOfTmpBuffer) const
{
char* endPtr = tmpBuffer + sizeOfTmpBuffer - 1;
char* endOfBuffer = AddToXmlStringUnsafe(tmpBuffer, level, endPtr);
endOfBuffer[0] = '\0';
XmlString ret(tmpBuffer);
return ret;
}
// TODO: those 2 saving functions are a bit messy. should probably make a separate one for the use of PlatformAPI
bool CXmlNode::saveToFile(const char* fileName)
{
if (!fileName)
{
return false;
}
{
AZ::IO::HandleType fileHandle = gEnv->pCryPak->FOpen(fileName, "wt");
if (fileHandle != AZ::IO::InvalidHandle)
{
#ifdef WIN32
XmlString xml = getXML(); // this would not work in consoles because the size limits in strings
const char* sxml = (const char*)xml;
gEnv->pCryPak->FWrite(sxml, xml.length(), fileHandle);
gEnv->pCryPak->FClose(fileHandle);
return true;
#else
constexpr size_t chunkSizeBytes = (15 * 1024);
bool ret = saveToFile(fileName, chunkSizeBytes, fileHandle);
gEnv->pCryPak->FClose(fileHandle);
return ret;
#endif
}
return false;
}
}
bool CXmlNode::saveToFile([[maybe_unused]] const char* fileName, size_t chunkSize, AZ::IO::HandleType fileHandle)
{
#ifdef WIN32
CrySetFileAttributes(fileName, 0x00000080); // FILE_ATTRIBUTE_NORMAL
#endif //WIN32
if (chunkSize < 256 * 1024) // make at least 256k
{
chunkSize = 256 * 1024;
}
XmlString xml;
xml.assign ("");
xml.reserve(chunkSize * 2); // we reserve double memory, as writing in chunks is not really writing in fixed blocks but a bit fuzzy
auto pCryPak = gEnv->pCryPak;
if (fileHandle == AZ::IO::InvalidHandle)
{
return false;
}
AddToXmlString(xml, 0, fileHandle, chunkSize);
size_t len = xml.length();
if (len > 0)
{
pCryPak->FWrite(xml.c_str(), len, 1, fileHandle);
}
xml.clear(); // xml.resize(0) would not reclaim memory
return true;
}
/**
******************************************************************************
* XmlParserImp class.
******************************************************************************
*/
class XmlParserImp
: public IXmlStringPool
{
public:
explicit XmlParserImp(bool bReuseStrings);
~XmlParserImp();
void ParseBegin(bool bCleanPools);
XmlNodeRef ParseFile(const char* filename, XmlString& errorString, bool bCleanPools);
XmlNodeRef ParseBuffer(const char* buffer, size_t bufLen, XmlString& errorString, bool bCleanPools, bool bSuppressWarnings = false);
void ParseEnd();
// Add new string to pool.
const char* AddString(const char* str) { return m_stringPool.Append(str, (int)strlen(str)); }
protected:
void onStartElement(const char* tagName, const char** atts);
void onEndElement(const char* tagName);
void onRawData(const char* data);
static void startElement(void* userData, const char* name, const char** atts)
{
((XmlParserImp*)userData)->onStartElement(name, atts);
}
static void endElement(void* userData, const char* name)
{
((XmlParserImp*)userData)->onEndElement(name);
}
static void characterData(void* userData, const char* s, int len)
{
char str[32700];
if (len > sizeof(str) - 1)
{
assert(0);
len = sizeof(str) - 1;
}
// Note that XML buffer userData has no terminating '\0'.
memcpy(str, s, len);
str[len] = 0;
((XmlParserImp*)userData)->onRawData(str);
}
void CleanStack();
struct SStackEntity
{
XmlNodeRef node;
std::vector<IXmlNode*> childs; //TODO: is it worth lazily initializing this, like CXmlNode::m_pChilds?
};
// First node will become root node.
std::vector<SStackEntity> m_nodeStack;
int m_nNodeStackTop;
XmlNodeRef m_root;
XML_Parser m_parser;
CSimpleStringPool m_stringPool;
};
//////////////////////////////////////////////////////////////////////////
void XmlParserImp::CleanStack()
{
m_nNodeStackTop = 0;
for (int i = 0, num = static_cast<int>(m_nodeStack.size()); i < num; i++)
{
m_nodeStack[i].node = 0;
m_nodeStack[i].childs.resize(0);
}
}
/**
******************************************************************************
* XmlParserImp
******************************************************************************
*/
void XmlParserImp::onStartElement(const char* tagName, const char** atts)
{
CXmlNode* pCNode = new CXmlNode;
pCNode->m_pStringPool = this;
pCNode->m_pStringPool->AddRef();
pCNode->m_tag = AddString(tagName);
XmlNodeRef node = pCNode;
m_nNodeStackTop++;
if (m_nNodeStackTop >= (int)m_nodeStack.size())
{
m_nodeStack.resize(m_nodeStack.size() * 2);
}
m_nodeStack[m_nNodeStackTop].node = pCNode;
m_nodeStack[m_nNodeStackTop - 1].childs.push_back(pCNode);
if (!m_root)
{
m_root = node;
}
else
{
pCNode->m_parent = (IXmlNode*)m_nodeStack[m_nNodeStackTop - 1].node;
node->AddRef(); // Childs need to be add refed.
}
node->setLine(XML_GetCurrentLineNumber((XML_Parser)m_parser));
// Call start element callback.
int i = 0;
int numAttrs = 0;
while (atts[i] != 0)
{
numAttrs++;
i += 2;
}
if (numAttrs > 0)
{
i = 0;
if (!pCNode->m_pAttributes)
{
pCNode->m_pAttributes = new XmlAttributes;
}
XmlAttributes& nodeAtts = *(pCNode->m_pAttributes);
nodeAtts.resize(numAttrs);
int nAttr = 0;
while (atts[i] != 0)
{
nodeAtts[nAttr].key = AddString(atts[i]);
nodeAtts[nAttr].value = AddString(atts[i + 1]);
nAttr++;
i += 2;
}
// Sort attributes.
//std::sort( pCNode->m_attributes.begin(),pCNode->m_attributes.end() );
}
}
void XmlParserImp::onEndElement([[maybe_unused]] const char* tagName)
{
assert(m_nNodeStackTop > 0);
if (m_nNodeStackTop > 0)
{
// Copy current childs to the parent.
SStackEntity& entry = m_nodeStack[m_nNodeStackTop];
CXmlNode* currNode = static_cast<CXmlNode*>(static_cast<IXmlNode*>(entry.node));
if (!entry.childs.empty())
{
if (!currNode->m_pChilds)
{
currNode->m_pChilds = new CXmlNode::XmlNodes;
}
*currNode->m_pChilds = entry.childs;
}
entry.childs.resize(0);
entry.node = NULL;
}
m_nNodeStackTop--;
}
void XmlParserImp::onRawData(const char* data)
{
assert(m_nNodeStackTop >= 0);
if (m_nNodeStackTop >= 0)
{
size_t len = strlen(data);
if (len > 0)
{
if (strspn(data, "\r\n\t ") != len)
{
CXmlNode* node = (CXmlNode*)(IXmlNode*)m_nodeStack[m_nNodeStackTop].node;
if (*node->m_content != '\0')
{
node->m_content = m_stringPool.ReplaceString(node->m_content, data);
}
else
{
node->m_content = AddString(data);
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
XmlParserImp::XmlParserImp(bool bReuseStrings)
: m_nNodeStackTop(0)
, m_parser(NULL)
, m_stringPool(bReuseStrings)
{
m_nodeStack.resize(32);
CleanStack();
}
XmlParserImp::~XmlParserImp()
{
ParseEnd();
}
namespace
{
void* custom_xml_malloc(size_t nSize)
{
return CryModuleMalloc(nSize);
}
void* custom_xml_realloc(void* p, size_t nSize)
{
return CryModuleRealloc(p, nSize);
}
void custom_xml_free(void* p)
{
CryModuleFree(p);
}
}
//////////////////////////////////////////////////////////////////////////
void XmlParserImp::ParseBegin(bool bCleanPools)
{
m_root = 0;
CleanStack();
if (bCleanPools)
{
m_stringPool.Clear();
}
XML_Memory_Handling_Suite memHandler;
memHandler.malloc_fcn = custom_xml_malloc;
memHandler.realloc_fcn = custom_xml_realloc;
memHandler.free_fcn = custom_xml_free;
m_parser = XML_ParserCreate_MM(NULL, &memHandler, NULL);
XML_SetUserData(m_parser, this);
XML_SetElementHandler(m_parser, startElement, endElement);
XML_SetCharacterDataHandler(m_parser, characterData);
XML_SetEncoding(m_parser, "utf-8");
}
//////////////////////////////////////////////////////////////////////////
void XmlParserImp::ParseEnd()
{
if (m_parser)
{
XML_ParserFree(m_parser);
}
m_parser = 0;
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef XmlParserImp::ParseBuffer(const char* buffer, size_t bufLen, XmlString& errorString, bool bCleanPools, bool bSuppressWarnings)
{
static const char* const errorPrefix = "XML parser: ";
XmlNodeRef root = 0;
// Let's try to parse the buffer as binary XML
{
XMLBinary::XMLBinaryReader reader;
XMLBinary::XMLBinaryReader::EResult result;
root = reader.LoadFromBuffer(XMLBinary::XMLBinaryReader::eBufferMemoryHandling_MakeCopy, buffer, bufLen, result);
if (root)
{
return root;
}
if (result != XMLBinary::XMLBinaryReader::eResult_NotBinXml)
{
const char* const str = reader.GetErrorDescription();
errorString = str;
if (!bSuppressWarnings)
{
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s%s (data size: %u)", errorPrefix, str, static_cast<unsigned>(bufLen));
}
return 0;
}
}
// This is not binary XML, so let's use text XML parser.
{
ParseBegin(bCleanPools);
m_stringPool.SetBlockSize(static_cast<unsigned>(bufLen) / 16);
if (XML_Parse(m_parser, buffer, static_cast<int>(bufLen), 1))
{
root = m_root;
}
else
{
char str[1024];
sprintf_s(str, "%s%s at line %d", errorPrefix, XML_ErrorString(XML_GetErrorCode(m_parser)), (int)XML_GetCurrentLineNumber(m_parser));
errorString = str;
if (!bSuppressWarnings)
{
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
}
}
m_root = 0;
ParseEnd();
}
return root;
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef XmlParserImp::ParseFile(const char* filename, XmlString& errorString, bool bCleanPools)
{
if (!filename)
{
return 0;
}
static const char* const errorPrefix = "XML reader: ";
XmlNodeRef root = 0;
char* pFileContents = 0;
size_t fileSize = 0;
char str[1024];
AZStd::fixed_string<256> adjustedFilename;
AZStd::fixed_string<256> pakPath;
if (fileSize <= 0)
{
CCryFile xmlFile;
if (!xmlFile.Open(filename, "rb"))
{
sprintf_s(str, "%sCan't open file (%s)", errorPrefix, filename);
errorString = str;
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return 0;
}
fileSize = xmlFile.GetLength();
if (fileSize <= 0)
{
sprintf_s(str, "%sFile is empty (%s)", errorPrefix, filename);
errorString = str;
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return 0;
}
pFileContents = new char[fileSize];
if (!pFileContents)
{
sprintf_s(str, "%sCan't allocate %u bytes of memory (%s)", errorPrefix, static_cast<unsigned>(fileSize), filename);
errorString = str;
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return 0;
}
if (xmlFile.ReadRaw(pFileContents, fileSize) != fileSize)
{
delete [] pFileContents;
sprintf_s(str, "%sCan't read file (%s)", errorPrefix, filename);
errorString = str;
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return 0;
}
adjustedFilename = xmlFile.GetAdjustedFilename();
AZStd::replace(adjustedFilename.begin(), adjustedFilename.end(), '\\', '/');
pakPath = xmlFile.GetPakPath();
AZStd::replace(pakPath.begin(), pakPath.end(), '\\', '/');
}
XMLBinary::XMLBinaryReader reader;
XMLBinary::XMLBinaryReader::EResult result;
root = reader.LoadFromBuffer(XMLBinary::XMLBinaryReader::eBufferMemoryHandling_TakeOwnership, pFileContents, fileSize, result);
if (root)
{
return root;
}
if (result != XMLBinary::XMLBinaryReader::eResult_NotBinXml)
{
delete [] pFileContents;
sprintf_s(str, "%s%s (%s)", errorPrefix, reader.GetErrorDescription(), filename);
errorString = str;
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
return 0;
}
else
{
// not binary XML - refuse to load if in scripts dir and not in bin xml to help reduce hacking
// wish we could compile the text xml parser out, but too much work to get everything moved over
constexpr AZStd::fixed_string<32> strScripts{"Scripts/"};
// exclude files and PAKs from Mods folder
constexpr AZStd::fixed_string<8> modsStr{"Mods/"};
if (_strnicmp(filename, strScripts.c_str(), strScripts.length()) == 0 &&
_strnicmp(adjustedFilename.c_str(), modsStr.c_str(), modsStr.length()) != 0 &&
_strnicmp(pakPath.c_str(), modsStr.c_str(), modsStr.length()) != 0)
{
#ifdef _RELEASE
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "Non binary XML found in scripts dir (%s)", filename);
#endif
}
}
{
ParseBegin(bCleanPools);
m_stringPool.SetBlockSize(static_cast<unsigned>(fileSize / 16));
if (XML_Parse(m_parser, pFileContents, static_cast<int>(fileSize), 1))
{
root = m_root;
}
else
{
sprintf_s(str, "%s%s at line %d (%s)", errorPrefix, XML_ErrorString(XML_GetErrorCode(m_parser)), (int)XML_GetCurrentLineNumber(m_parser), filename);
errorString = str;
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "%s", str);
}
m_root = 0;
ParseEnd();
}
delete [] pFileContents;
return root;
}
XmlParser::XmlParser(bool bReuseStrings)
{
m_nRefCount = 0;
m_pImpl = new XmlParserImp(bReuseStrings);
m_pImpl->AddRef();
}
XmlParser::~XmlParser()
{
m_pImpl->Release();
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef XmlParser::ParseBuffer(const char* buffer, int nBufLen, bool bCleanPools, bool bSuppressWarnings)
{
m_errorString = "";
return m_pImpl->ParseBuffer(buffer, nBufLen, m_errorString, bCleanPools, bSuppressWarnings);
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef XmlParser::ParseFile(const char* filename, bool bCleanPools)
{
m_errorString = "";
return m_pImpl->ParseFile(filename, m_errorString, bCleanPools);
}
//////////////////////////////////////////////////////////////////////////
//
// Implements special reusable XmlNode for XmlNode pool
//
//////////////////////////////////////////////////////////////////////////
CXmlNodeReuse::CXmlNodeReuse(const char* tag, CXmlNodePool* pPool)
: m_pPool(pPool)
{
SAFE_RELEASE(m_pStringPool);
m_pStringPool = m_pPool->GetStringPool();
m_pStringPool->AddRef();
m_tag = m_pStringPool->AddString(tag);
}
void CXmlNodeReuse::Release()
{
m_pPool->OnRelease(m_nRefCount, this);
CXmlNode::Release();
}
//////////////////////////////////////////////////////////////////////////
//
// Pool of reusable XML nodes with shared string pool
//
//////////////////////////////////////////////////////////////////////////
CXmlNodePool::CXmlNodePool(unsigned int nBlockSize, bool bReuseStrings)
{
m_pStringPool = new CXmlStringPool(bReuseStrings);
assert(m_pStringPool != 0);
// in order to avoid memory fragmentation
// allocates 1Mb buffer for shared string pool
static_cast<CXmlStringPool*>(m_pStringPool)->SetBlockSize(nBlockSize);
m_pStringPool->AddRef();
m_nAllocated = 0;
}
CXmlNodePool::~CXmlNodePool()
{
while (!m_pNodePool.empty())
{
CXmlNodeReuse* pNode = m_pNodePool.top();
m_pNodePool.pop();
pNode->Release();
}
m_pStringPool->Release();
}
XmlNodeRef CXmlNodePool::GetXmlNode(const char* sNodeName)
{
CXmlNodeReuse* pNode = 0;
// NOTE: at the moment xml node pool is dedicated for statistics nodes only
// first at all check if we have already free node
if (!m_pNodePool.empty())
{
pNode = m_pNodePool.top();
m_pNodePool.pop();
// init it to new node name
pNode->setTag(sNodeName);
m_nAllocated++;
//if (0 == m_nAllocated % 1000)
//CryLog("[CXmlNodePool]: already reused nodes [%d]", m_nAllocated);
}
else
{
// there is no free nodes so create new one
// later it will be reused as soon as no external references left
pNode = new CXmlNodeReuse(sNodeName, this);
assert(pNode != 0);
// increase ref counter for reusing node later
pNode->AddRef();
m_nAllocated++;
//if (0 == m_nAllocated % 1000)
//CryLog("[CXmlNodePool]: already allocated nodes [%d]", m_nAllocated);
}
return pNode;
}
void CXmlNodePool::OnRelease(int iRefCount, void* pThis)
{
// each reusable node call OnRelease before parent release
// since we keep reference on xml node so when ref count equals
// to 2 means that it is last external object releases reference
// to reusable node and it can be save for reuse later
if (2 == iRefCount)
{
CXmlNodeReuse* pNode = static_cast<CXmlNodeReuse*>(pThis);
pNode->removeAllChilds();
pNode->removeAllAttributes();
m_pNodePool.push(pNode);
// decrease totally allocated by xml node pool counter
// when counter equals to zero it means that all external
// objects do not have references to allocated reusable node
// at that point it is safe to clear shared string pool
m_nAllocated--;
if (0 == m_nAllocated)
{
//CryLog("[CXmlNodePool]: clear shared string pool");
static_cast<CXmlStringPool*>(m_pStringPool)->Clear();
}
}
}
#undef SCOPED_LOCALE_RESETTER