Files
o3de/Code/Legacy/CrySystem/XML/xml.cpp
T
Artur K 56904d1799 Cry Legacy: the Lessening (#5961)
* Generic Cleanup

Removals:

* Unused cvars,member variables and defines
* Windows media center edition support routines
* CSystem - removed debug_GetCallStackRaw/GetRootWindowMessageHandler/
UnloadDLL/ShutdownModuleLibraries

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Fix CryMessageBox return values in Windows build

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove named selection group and a few smaller unused functionalities

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove ObjectManager export functionality + 2 deprecated functions

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove object legacy freeze/hide support from ObjectManager

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove ObjectManager duplicate name dection, as well as object renaming

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove ObjectManager serialization and selection callbacks

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* ObjectManager - remove MoveObjects/HitTestObject/EndEditParams and related members/functions

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove a bunch of unused clone related functionality + misc

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* ObjectManager - misc removals

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* ObjectManager - more removals

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove unused object selection/retrieval in ObjectManager and LoadObjects in ObjectLoader

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject - remove unused material layers mask support

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* More CBaseObject cleanups.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject - remove SubObj functions and IMouseCreateCallback

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove unused procedural floor management, helper scale and tags.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject - remove more unused methods.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Removals in multiple places.

CBaseObject - unused `OBJTYPE_DUMMY` flag removed, member `IsSameClass`
CObjectArchive removed unused methods/members
CObjectManager removed unused `Update` method

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* More removals

IDisplayViewport - HitTestLine/GetGridStep/setHitcontext/
GetConstructionPlane
Unused Cry_Matrix44 template specializations.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* O3DE.exe Project-Centric "Open Editor" fix (#5852)

* The O3DE.exe Open Editor button now attempts to open the Editor in the
build directory of the project being opened.

If their is no Editor within the build directory of the Project, it uses
the Editor.exe in the current O3DE.exe executable directory if it exists

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Engine .gitignore now ignores the build directory if placed in the
AutomatedTesting project

Previously it was just ignoring a `[Bb]uild` directory if it was
directly within the engine root.
This change matches the behavior of the project templates.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Renamed the ProjectUtils GetEditorDirectory function to
GetEditorExecutablePath

Added a platform specific implementation for retrieving the path to the
Editor executable in the GetEditorExectuablePath function.

It first attempts to locate the Editor via checking the project build
directory for an Editor executable before falling back to checking the
binary directory of the currently running O3DE executable.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Correct the MacOS GetEditorExecutablePath to return the Editor path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Adding missing C++20 std::erase implementations (#5735)

There were already implementations for std::erase_if.
This adds the counterpart AZStd::erase versions

resolves #5734

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* More removals and refactors

`ShowMessage` and `CryMessageBox` return void now
Simplify code in `CSystem::WarningV`
Remove unused `CryGetTicksPerSec`
Remove unused WinBase functionality
Replace `nanosleep` call in WinBase with `std::this_thread::sleep_for`
Remove unused Win32Wrapper routines
Remove unused IFunctorBase.h and IEntityObjectListener.h
Fix VectorAndArray.cpp compilation
Use QMessageBox instead of CryMessageBox in the editor.
Remove empty ArchiveVars platform specific files

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Fix test code.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove an unused  function

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject and undo description removals.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* A bunch of removals

* Remove IRenderNode
* Remove editor's KDTree
* Remove StatObjBus, InstanceStatObjEventBus::ReleaseData is redundant (
same functionality in the only available handler is triggered by
`OnCryEditorCloseScene`)
* Remove CExportManager::AddStatObj/AddMeshes/AddMesh,
IExportManager::ExportSingleStatObj
* Remove CIconManager/IIconManager::GetObject
* Remove CBaseObject::IntersectRayMesh
* Remove IIndexedMesh and related structs.
* Unused IUndoObject::GetDescription and all derived implementations.
* Unused CUndoBaseLibrary/CUndoBaseLibraryManager
* Unused Matrix34_tpl typedefs
* Legacy Xml classes cleanup

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove unused `Vec3ToVector3D` helper

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* fix - remove unused static variable

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

Co-authored-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-11-29 11:21:06 -07:00

1741 lines
45 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;
}
}
}
}
//! 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::setParent(const XmlNodeRef& inNewParent)
{
// note, parent ptrs are not ref counted
m_parent = inNewParent;
}
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;
}
//////////////////////////////////////////////////////////////////////////
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)
{
auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIoBase != nullptr, "FileIOBase is expected to be initialized for CXmlNode");
size_t len = xml.length();
if (len >= chunkSize)
{
fileIoBase->Write(fileHandle, xml.c_str(), len);
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;
}
// 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)
{
CrySetFileAttributes(fileName, FILE_ATTRIBUTE_NORMAL);
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 fileIoBase = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIoBase != nullptr, "FileIOBase is expected to be initialized for CXmlNode");
if (fileHandle == AZ::IO::InvalidHandle)
{
return false;
}
AddToXmlString(xml, 0, fileHandle, chunkSize);
size_t len = xml.length();
if (len > 0)
{
fileIoBase->Write(fileHandle, xml.c_str(), len);
}
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;
}
AZ::IO::FixedMaxPath resolvedPath(AZ::IO::PosixPathSeparator);
auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIoBase != nullptr, "FileIOBase is expected to be initialized for CXmlNode");
if (fileIoBase->ResolvePath(resolvedPath, xmlFile.GetFilename()))
{
adjustedFilename = resolvedPath.MakePreferred().Native();
}
if (fileIoBase->ResolvePath(resolvedPath, xmlFile.GetPakPath()))
{
pakPath = resolvedPath.MakePreferred().Native();
}
}
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