Index: build/premake/premake5.lua =================================================================== --- build/premake/premake5.lua +++ build/premake/premake5.lua @@ -731,6 +731,7 @@ "ps/scripting", "network/scripting", "ps/GameSetup", + "ps/XMB", "ps/XML", "soundmanager", "soundmanager/data", Index: source/graphics/ObjectBase.cpp =================================================================== --- source/graphics/ObjectBase.cpp +++ source/graphics/ObjectBase.cpp @@ -195,7 +195,7 @@ if (variant.GetNodeName() != el_variant) { - LOGERROR("Invalid variant format (unrecognised root element '%s')", XeroFile.GetElementString(variant.GetNodeName()).c_str()); + LOGERROR("Invalid variant format (unrecognised root element '%s')", XeroFile.GetElementString(variant.GetNodeName())); return false; } @@ -821,7 +821,7 @@ if (root.GetNodeName() != el_actor && root.GetNodeName() != el_qualitylevels) { LOGERROR("Invalid actor format (actor '%s', unrecognised root element '%s')", - pathname.string8().c_str(), XeroFile.GetElementString(root.GetNodeName()).c_str()); + pathname.string8().c_str(), XeroFile.GetElementString(root.GetNodeName())); return false; } Index: source/graphics/TerrainProperties.cpp =================================================================== --- source/graphics/TerrainProperties.cpp +++ source/graphics/TerrainProperties.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -28,7 +28,6 @@ #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" -#include "ps/XML/XeroXMB.h" #include "ps/XML/Xeromyces.h" CTerrainProperties::CTerrainProperties(CTerrainPropertiesPtr parent): Index: source/graphics/TerrainTextureEntry.cpp =================================================================== --- source/graphics/TerrainTextureEntry.cpp +++ source/graphics/TerrainTextureEntry.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -71,7 +71,7 @@ if (root.GetNodeName() != el_terrain) { - LOGERROR("Invalid terrain format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()).c_str()); + LOGERROR("Invalid terrain format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName())); return; } Index: source/graphics/TextureConverter.cpp =================================================================== --- source/graphics/TextureConverter.cpp +++ source/graphics/TextureConverter.cpp @@ -227,7 +227,7 @@ } else { - LOGERROR("Invalid attribute name ", XeroFile.GetAttributeString(attr.Name).c_str()); + LOGERROR("Invalid attribute name ", XeroFile.GetAttributeString(attr.Name)); } } Index: source/gui/CGUI.cpp =================================================================== --- source/gui/CGUI.cpp +++ source/gui/CGUI.cpp @@ -532,7 +532,7 @@ return; XMBElement node = XeroFile.GetRoot(); - CStr root_name(XeroFile.GetElementString(node.GetNodeName())); + std::string_view root_name(XeroFile.GetElementStringView(node.GetNodeName())); if (root_name == "objects") Xeromyces_ReadRootObjects(node, &XeroFile, Paths); @@ -543,7 +543,7 @@ else if (root_name == "setup") Xeromyces_ReadRootSetup(node, &XeroFile); else - LOGERROR("CGUI::LoadXmlFile encountered an unknown XML root node type: %s", root_name.c_str()); + LOGERROR("CGUI::LoadXmlFile encountered an unknown XML root node type: %s", root_name.data()); } void CGUI::LoadedXmlFiles() @@ -595,7 +595,11 @@ { for (XMBElement child : Element.GetChildNodes()) { +<<<<<<< HEAD CStr name(pFile->GetElementString(child.GetNodeName())); +======= + std::string_view name(file.GetElementStringView(child.GetNodeName())); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. if (name == "scrollbar") Xeromyces_ReadScrollBarStyle(child, pFile); @@ -989,7 +993,11 @@ for (XMBElement child : Element.GetChildNodes()) { +<<<<<<< HEAD CStr ElementName(pFile->GetElementString(child.GetNodeName())); +======= + std::string_view ElementName(file.GetElementStringView(child.GetNodeName())); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. if (ElementName == "image") Xeromyces_ReadImage(child, pFile, *Sprite); @@ -1026,7 +1034,11 @@ for (XMBAttribute attr : Element.GetAttributes()) { +<<<<<<< HEAD CStr attr_name(pFile->GetAttributeString(attr.Name)); +======= + std::string_view attr_name(file.GetAttributeStringView(attr.Name)); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. CStrW attr_value(attr.Value.FromUTF8()); if (attr_name == "texture") @@ -1109,7 +1121,11 @@ // Look for effects for (XMBElement child : Element.GetChildNodes()) { +<<<<<<< HEAD CStr ElementName(pFile->GetElementString(child.GetNodeName())); +======= + std::string_view ElementName(file.GetElementStringView(child.GetNodeName())); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. if (ElementName == "effect") { if (Image->m_Effects) @@ -1131,7 +1147,11 @@ { for (XMBAttribute attr : Element.GetAttributes()) { +<<<<<<< HEAD CStr attr_name(pFile->GetAttributeString(attr.Name)); +======= + std::string_view attr_name(file.GetAttributeStringView(attr.Name)); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. if (attr_name == "add_color") { @@ -1152,14 +1172,18 @@ for (XMBAttribute attr : Element.GetAttributes()) { +<<<<<<< HEAD CStr attr_name(pFile->GetAttributeString(attr.Name)); +======= + std::string_view attr_name(file.GetAttributeStringView(attr.Name)); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. // The "name" setting is actually the name of the style // and not a new default if (attr_name == "name") name = attr.Value; else - style.m_SettingsDefaults.emplace(attr_name, attr.Value.FromUTF8()); + style.m_SettingsDefaults.emplace(std::string(attr_name), attr.Value.FromUTF8()); } m_Styles.erase(name); @@ -1179,7 +1203,11 @@ for (XMBAttribute attr : Element.GetAttributes()) { +<<<<<<< HEAD CStr attr_name = pFile->GetAttributeString(attr.Name); +======= + std::string_view attr_name(file.GetAttributeStringView(attr.Name)); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. CStr attr_value(attr.Value); if (attr_value == "null") @@ -1256,7 +1284,11 @@ for (XMBAttribute attr : Element.GetAttributes()) { +<<<<<<< HEAD CStr attr_name(pFile->GetAttributeString(attr.Name)); +======= + std::string_view attr_name(file.GetAttributeStringView(attr.Name)); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. CStr attr_value(attr.Value); if (attr_value == "null") @@ -1288,13 +1320,17 @@ for (XMBAttribute attr : Element.GetAttributes()) { +<<<<<<< HEAD CStr attr_name(pFile->GetAttributeString(attr.Name)); +======= + std::string_view attr_name(file.GetAttributeStringView(attr.Name)); +>>>>>>> 0e051f3b54... Bump XMB to version 4, improve efficiency of using strings. CStr attr_value(attr.Value); if (attr_name == "name") object->SetName("__tooltip_" + attr_value); else - object->SetSettingFromString(attr_name, attr_value.FromUTF8(), true); + object->SetSettingFromString(std::string(attr_name), attr_value.FromUTF8(), true); } if (!AddObject(*m_BaseObject, *object)) Index: source/gui/ObjectTypes/COList.cpp =================================================================== --- source/gui/ObjectTypes/COList.cpp +++ source/gui/ObjectTypes/COList.cpp @@ -203,13 +203,13 @@ for (XMBAttribute attr : child.GetAttributes()) { - CStr attr_name(pFile->GetAttributeString(attr.Name)); + std::string_view attr_name(pFile.GetAttributeStringView(attr.Name)); CStr attr_value(attr.Value); if (attr_name == "color") { if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), column.m_TextColor)) - LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str()); } else if (attr_name == "id") { @@ -219,7 +219,7 @@ { bool hidden = false; if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), hidden)) - LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str()); else column.m_Hidden = hidden; } @@ -227,7 +227,7 @@ { float width; if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), width)) - LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str()); else { // Check if it's a relative value, and save as decimal if so. Index: source/ps/XMB/XMBData.h =================================================================== --- source/ps/XMB/XMBData.h +++ source/ps/XMB/XMBData.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -28,8 +28,6 @@ * Can't correctly handle mixed text/elements inside elements - "
Text
" and "
Text
" are considered identical. - * Tries to avoid using strings - you usually have to load the - numeric IDs and use them instead. Theoretical file structure: @@ -37,13 +35,17 @@ char Header[4]; // because everyone has one; currently "XMB0" u32 Version; - int ElementNameCount; - ZStr8 ElementNames[]; + int OffsetFromStartToElementNames; + int ElementNameCount; + int OffsetFromStartToAttributeNames; int AttributeNameCount; + + XMB_Node Root; + + ZStr8 ElementNames[]; ZStr8 AttributeNames[]; - XMB_Node Root; } XMB_Node { @@ -83,71 +85,57 @@ #ifndef INCLUDED_XEROXMB #define INCLUDED_XEROXMB -// Define to use a std::map for name lookups rather than a linear search. -// (The map is usually slower.) -//#define XERO_USEMAP - -#include - -#ifdef XERO_USEMAP -# include -#endif - #include "ps/CStr.h" -// File headers, to make sure it doesn't try loading anything other than an XMB -extern const char* HeaderMagicStr; -extern const char* UnfinishedHeaderMagicStr; -extern const u32 XMBVersion; +#include +#include + +class CXeromyces; +class XMBStorage; class XMBElement; class XMBElementList; class XMBAttributeList; -class XMBFile +class XMBData { public: - XMBFile() : m_Pointer(NULL) {} + XMBData() : m_Pointer(nullptr) {} - // Initialise from the contents of an XMB file. - // FileData must remain allocated and unchanged while - // the XMBFile is being used. - // @return indication of success; main cause for failure is attempting to - // load a partially valid XMB file (e.g. if the game was interrupted - // while writing it), which we detect by checking the magic string. - // It also fails when trying to load an XMB file with a different version. - bool Initialise(const char* FileData); + /* + * Initialise from the contents of an XMBStorage. + * @param doc must remain allocated and unchanged while + * the XMBData is being used. + * @return indication of success; main cause for failure is attempting to + * load a partially valid XMB file (e.g. if the game was interrupted + * while writing it), which we detect by checking the magic string. + * It also fails when trying to load an XMB file with a different version. + */ + bool Initialise(const XMBStorage& doc); // Returns the root element XMBElement GetRoot() const; - // Returns internal ID for a given element/attribute string. int GetElementID(const char* Name) const; int GetAttributeID(const char* Name) const; - // For lazy people (e.g. me) when speed isn't vital: + // Returns element/attribute string for a given internal ID. + const char* GetElementString(const int ID) const; + const char* GetAttributeString(const int ID) const; - // Returns element/attribute string for a given internal ID - std::string GetElementString(const int ID) const; - std::string GetAttributeString(const int ID) const; + std::string_view GetElementStringView(const int ID) const; + std::string_view GetAttributeStringView(const int ID) const; private: const char* m_Pointer; -#ifdef XERO_USEMAP - std::map m_ElementNames; - std::map m_AttributeNames; -#else int m_ElementNameCount; int m_AttributeNameCount; const char* m_ElementPointer; const char* m_AttributePointer; -#endif - - std::string ReadZStr8(); }; class XMBElement Index: source/ps/XMB/XMBData.cpp =================================================================== --- source/ps/XMB/XMBData.cpp +++ source/ps/XMB/XMBData.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -17,16 +17,15 @@ #include "precompiled.h" -#include "Xeromyces.h" - #include "lib/byte_order.h" // FOURCC_LE +#include "ps/XMB/XMBStorage.h" +#include "ps/XML/Xeromyces.h" -// external linkage (also used by Xeromyces.cpp) -const char* HeaderMagicStr = "XMB0"; -const char* UnfinishedHeaderMagicStr = "XMBu"; +const char* XMBStorage::HeaderMagicStr = "XMB0"; +const char* XMBStorage::UnfinishedHeaderMagicStr = "XMBu"; // Arbitrary version number - change this if we update the code and // need to invalidate old users' caches -const u32 XMBVersion = 3; +const u32 XMBStorage::XMBVersion = 4; template static inline T read(const void* ptr) @@ -36,85 +35,42 @@ return ret; } -bool XMBFile::Initialise(const char* FileData) +bool XMBData::Initialise(const XMBStorage& doc) { - m_Pointer = FileData; + const char* start = reinterpret_cast(doc.m_Buffer.get()); + m_Pointer = start; char Header[5] = { 0 }; strncpy_s(Header, 5, m_Pointer, 4); m_Pointer += 4; - // (c.f. @return documentation of this function) - if(!strcmp(Header, UnfinishedHeaderMagicStr)) + + if (strcmp(Header, XMBStorage::UnfinishedHeaderMagicStr) == 0) return false; - ENSURE(!strcmp(Header, HeaderMagicStr) && "Invalid XMB header!"); + ENSURE(strcmp(Header, XMBStorage::HeaderMagicStr) == 0 && "Invalid XMB header!"); u32 Version = read(m_Pointer); m_Pointer += 4; - if (Version != XMBVersion) + if (Version != XMBStorage::XMBVersion) return false; - int i; - // FIXME Check that m_Pointer doesn't end up past the end of the buffer // (it shouldn't be all that dangerous since we're only doing read-only // access, but it might crash on an invalid file, reading a couple of // billion random element names from RAM) -#ifdef XERO_USEMAP - // Build a std::map of all the names->ids - u32 ElementNameCount = read(m_Pointer); m_Pointer += 4; - for (i = 0; i < ElementNameCount; ++i) - m_ElementNames[ReadZStr8()] = i; - - u32 AttributeNameCount = read(m_Pointer); m_Pointer += 4; - for (i = 0; i < AttributeNameCount; ++i) - m_AttributeNames[ReadZStr8()] = i; -#else - // Ignore all the names for now, and skip over them - // (remembering the position of the first) + m_ElementPointer = start + read(m_Pointer); m_Pointer += 4; m_ElementNameCount = read(m_Pointer); m_Pointer += 4; - m_ElementPointer = m_Pointer; - for (i = 0; i < m_ElementNameCount; ++i) - m_Pointer += 4 + read(m_Pointer); // skip over the string - + m_AttributePointer = start + read(m_Pointer); m_Pointer += 4; m_AttributeNameCount = read(m_Pointer); m_Pointer += 4; - m_AttributePointer = m_Pointer; - for (i = 0; i < m_AttributeNameCount; ++i) - m_Pointer += 4 + read(m_Pointer); // skip over the string -#endif - + // At this point m_Pointer points to the element start, as expected. return true; // success } -std::string XMBFile::ReadZStr8() -{ - int Length = read(m_Pointer); - m_Pointer += 4; - std::string String (m_Pointer); // reads up until the first NULL - m_Pointer += Length; - return String; -} - -XMBElement XMBFile::GetRoot() const +XMBElement XMBData::GetRoot() const { return XMBElement(m_Pointer); } - -#ifdef XERO_USEMAP - -int XMBFile::GetElementID(const char* Name) const -{ - return m_ElementNames[Name]; -} - -int XMBFile::GetAttributeID(const char* Name) const -{ - return m_AttributeNames[Name]; -} - -#else // #ifdef XERO_USEMAP - -int XMBFile::GetElementID(const char* Name) const +int XMBData::GetElementID(const char* Name) const { const char* Pos = m_ElementPointer; @@ -126,7 +82,7 @@ // See if this could be the right string, checking its // length and then its contents if (read(Pos) == len && strncasecmp(Pos+4, Name, len) == 0) - return i; + return static_cast(Pos - m_ElementPointer); // If not, jump to the next string Pos += 4 + read(Pos); } @@ -134,7 +90,7 @@ return -1; } -int XMBFile::GetAttributeID(const char* Name) const +int XMBData::GetAttributeID(const char* Name) const { const char* Pos = m_AttributePointer; @@ -146,35 +102,33 @@ // See if this could be the right string, checking its // length and then its contents if (read(Pos) == len && strncasecmp(Pos+4, Name, len) == 0) - return i; + return static_cast(Pos - m_AttributePointer); // If not, jump to the next string Pos += 4 + read(Pos); } // Failed return -1; } -#endif // #ifdef XERO_USEMAP / #else - -// Relatively inefficient, so only use when -// laziness overcomes the need for speed -std::string XMBFile::GetElementString(const int ID) const +const char* XMBData::GetElementString(const int ID) const { - const char* Pos = m_ElementPointer; - for (int i = 0; i < ID; ++i) - Pos += 4 + read(Pos); - return std::string(Pos+4); + return reinterpret_cast(m_ElementPointer + ID + 4); } -std::string XMBFile::GetAttributeString(const int ID) const +const char* XMBData::GetAttributeString(const int ID) const { - const char* Pos = m_AttributePointer; - for (int i = 0; i < ID; ++i) - Pos += 4 + read(Pos); - return std::string(Pos+4); + return reinterpret_cast(m_AttributePointer + ID + 4); } +std::string_view XMBData::GetElementStringView(const int ID) const +{ + return std::string_view(reinterpret_cast(m_ElementPointer + ID + 4), read(m_ElementPointer + ID) - 1); +} +std::string_view XMBData::GetAttributeStringView(const int ID) const +{ + return std::string_view(reinterpret_cast(m_AttributePointer + ID + 4), read(m_AttributePointer + ID) - 1); +} int XMBElement::GetNodeName() const { Index: source/ps/XMB/XMBStorage.h =================================================================== --- /dev/null +++ source/ps/XMB/XMBStorage.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2021 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_XMBSTORAGE +#define INCLUDED_XMBSTORAGE + +#include "scriptinterface/ScriptForward.h" + +#include + +typedef struct _xmlDoc xmlDoc; +typedef xmlDoc* xmlDocPtr; + +struct IVFS; +typedef std::shared_ptr PIVFS; + +class Path; +typedef Path VfsPath; + +/** + * Storage for XMBData + */ +class XMBStorage +{ +public: + // File headers, to make sure it doesn't try loading anything other than an XMB + static const char* HeaderMagicStr; + static const char* UnfinishedHeaderMagicStr; + static const u32 XMBVersion; + + XMBStorage() = default; + + bool ReadFromFile(const PIVFS& vfs, const VfsPath& filename); + bool LoadXMLDoc(const xmlDocPtr doc); + bool LoadJSValue(const ScriptInterface& scriptInterface, JS::HandleValue value, const std::string& rootName); + + std::shared_ptr m_Buffer; + size_t m_Size = 0; +}; + + +#endif // INCLUDED_XMBSTORAGE Index: source/ps/XMB/XMBStorage.cpp =================================================================== --- /dev/null +++ source/ps/XMB/XMBStorage.cpp @@ -0,0 +1,449 @@ +/* Copyright (C) 2021 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "XMBStorage.h" + +#include "lib/file/io/write_buffer.h" +#include "scriptinterface/ScriptExtraHeaders.h" +#include "scriptinterface/ScriptInterface.h" + +#include +#include + +namespace +{ +class XMBStorageWriter +{ +public: + template + bool Load(WriteBuffer& writeBuffer, Args&&... args); + + int GetElementName(const std::string& name) { return GetName(m_ElementSize, m_ElementIDs, name); } + int GetAttributeName(const std::string& name) { return GetName(m_AttributeSize, m_AttributeIDs, name); } + +protected: + int GetName(int& totalSize, std::unordered_map& names, const std::string& name) + { + int nameIdx = totalSize; + auto [iterator, inserted] = names.try_emplace(name, nameIdx); + if (inserted) + totalSize += name.size() + 5; // Add 1 for the null terminator & 4 for the size int. + return iterator->second; + } + + void OutputNames(WriteBuffer& writeBuffer, const std::unordered_map& names) const; + + template + bool OutputElements(WriteBuffer&, Args...) + { + static_assert(sizeof...(Args) != sizeof...(Args), "OutputElements must be specialized."); + } + + int m_ElementSize = 0; + int m_AttributeSize = 0; + std::unordered_map m_ElementIDs; + std::unordered_map m_AttributeIDs; +}; + +template +bool XMBStorageWriter::Load(WriteBuffer& writeBuffer, Args&&... args) +{ + // Header + writeBuffer.Append(XMBStorage::UnfinishedHeaderMagicStr, 4); + // Version + writeBuffer.Append(&XMBStorage::XMBVersion, 4); + + // Filled in below. + size_t elementPtr = writeBuffer.Size(); + writeBuffer.Append("????????", 8); + // Likewise with attributes. + size_t attributePtr = writeBuffer.Size(); + writeBuffer.Append("????????", 8); + + if (!OutputElements(writeBuffer, std::forward(args)...)) + return false; + + u32 data = writeBuffer.Size(); + writeBuffer.Overwrite(&data, 4, elementPtr); + data = m_ElementIDs.size(); + writeBuffer.Overwrite(&data, 4, elementPtr + 4); + OutputNames(writeBuffer, m_ElementIDs); + + data = writeBuffer.Size(); + writeBuffer.Overwrite(&data, 4, attributePtr); + data = m_AttributeIDs.size(); + writeBuffer.Overwrite(&data, 4, attributePtr + 4); + OutputNames(writeBuffer, m_AttributeIDs); + + // File is now valid, so insert correct magic string. + writeBuffer.Overwrite(XMBStorage::HeaderMagicStr, 4, 0); + + return true; +} + +void XMBStorageWriter::OutputNames(WriteBuffer& writeBuffer, const std::unordered_map& names) const +{ + std::vector> orderedElements; + for (const std::pair& n : names) + orderedElements.emplace_back(n); + std::sort(orderedElements.begin(), orderedElements.end(), [](const auto& a, const auto&b) { return a.second < b.second; }); + for (const std::pair& n : orderedElements) + { + u32 textLen = (u32)n.first.length() + 1; + writeBuffer.Append(&textLen, 4); + writeBuffer.Append((void*)n.first.c_str(), textLen); + } +} + +class JSNodeData +{ +public: + JSNodeData(const ScriptInterface& s) : scriptInterface(s), rq(s) {} + + bool Setup(XMBStorageWriter& xmb, JS::HandleValue value); + bool Output(WriteBuffer& writeBuffer, JS::HandleValue value) const; + + std::vector> m_Attributes; + std::vector>> m_Children; + + const ScriptInterface& scriptInterface; + const ScriptRequest rq; +}; + +template<> +bool XMBStorageWriter::OutputElements(WriteBuffer& writeBuffer, JSNodeData& data, const u32& nodeName, JS::HandleValue&& value) +{ + // Set up variables. + if (!data.Setup(*this, value)) + return false; + + size_t posLength = writeBuffer.Size(); + // Filled in later with the length of the element + writeBuffer.Append("????", 4); + + writeBuffer.Append(&nodeName, 4); + + u32 attrCount = static_cast(data.m_Attributes.size()); + writeBuffer.Append(&attrCount, 4); + + u32 childCount = data.m_Children.size(); + writeBuffer.Append(&childCount, 4); + + // Filled in later with the offset to the list of child elements + size_t posChildrenOffset = writeBuffer.Size(); + writeBuffer.Append("????", 4); + + data.Output(writeBuffer, value); + + // Output attributes + for (const std::pair attr : data.m_Attributes) + { + writeBuffer.Append(&attr.first, 4); + u32 attrLen = u32(attr.second.size())+1; + writeBuffer.Append(&attrLen, 4); + writeBuffer.Append((void*)attr.second.c_str(), attrLen); + } + + // Go back and fill in the child-element offset + u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4)); + writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset); + + // Output all child elements, making a copy since data will be overwritten. + std::vector>> children = data.m_Children; + for (const std::pair>& child : children) + { + JS::RootedValue val(data.rq.cx, child.second); + if (!OutputElements(writeBuffer, data, child.first, val)) + return false; + } + + // Go back and fill in the length + u32 length = (u32)(writeBuffer.Size() - posLength); + writeBuffer.Overwrite(&length, 4, posLength); + + return true; +} + +bool JSNodeData::Output(WriteBuffer& writeBuffer, JS::HandleValue value) const +{ + switch (JS_TypeOfValue(rq.cx, value)) + { + case JSTYPE_UNDEFINED: + case JSTYPE_NULL: + case JSTYPE_OBJECT: + { + writeBuffer.Append("\0\0\0\0", 4); + break; + } + case JSTYPE_STRING: + case JSTYPE_NUMBER: + { + std::string text; + if (!ScriptInterface::FromJSVal(rq, value, text)) + return false; + + // Output text, prefixed by length in bytes + if (text.length() == 0) + { + // No text; don't write much + writeBuffer.Append("\0\0\0\0", 4); + } + else + { + // Write length and line number and null-terminated text + u32 nodeLen = u32(4 + text.length() + 1); + writeBuffer.Append(&nodeLen, 4); + // Line 0 - not really relevant here. + writeBuffer.Append("\0\0\0\0", 4); + writeBuffer.Append((void*)text.c_str(), nodeLen-4); + } + + break; + } + default: + { + LOGERROR("Unsupported JS construct when parsing ParamNode"); + return false; + } + } + return true; +} + +bool JSNodeData::Setup(XMBStorageWriter& xmb, JS::HandleValue value) +{ + m_Attributes.clear(); + m_Children.clear(); + JSType valType = JS_TypeOfValue(rq.cx, value); + if (valType != JSTYPE_OBJECT) + return true; + + std::vector props; + if (!scriptInterface.EnumeratePropertyNames(value, true, props)) + { + LOGERROR("Failed to enumerate component properties."); + return false; + } + + for (const std::string& prop : props) + { + bool attrib = !prop.empty() && prop.front() == '@'; + + std::string_view name = prop; + if (!attrib && !prop.empty() && prop.back() == '@') + { + size_t idx = prop.substr(0, prop.size()-1).find_last_of('@'); + if (idx == std::string::npos) + { + LOGERROR("object key name cannot end with an '@' unless it is an index specifier."); + return false; + } + name = std::string_view(prop.c_str(), idx); + } + else if (attrib) + name = std::string_view(prop.c_str()+1, prop.length()-1); + + JS::RootedValue child(rq.cx); + if (!scriptInterface.GetProperty(value, prop.c_str(), &child)) + return false; + + if (!attrib) + { + bool isArray = false; + if (!JS::IsArrayObject(rq.cx, child, &isArray)) + return false; + if (isArray) + { + // Parse each array object as a child. + JS::RootedObject obj(rq.cx); + JS_ValueToObject(rq.cx, child, &obj); + u32 length; + JS::GetArrayLength(rq.cx, obj, &length); + for (size_t i = 0; i < length; ++i) + { + JS::RootedValue arrayChild(rq.cx); + scriptInterface.GetPropertyInt(child, i, &arrayChild); + m_Children.emplace_back(xmb.GetElementName(std::string(name)), arrayChild); + } + continue; + } + m_Children.emplace_back(xmb.GetElementName(std::string(name)), child); + continue; + } + + std::string attrVal; + if (!ScriptInterface::FromJSVal(rq, child, attrVal)) + { + LOGERROR("Attributes must be convertible to string"); + return false; + } + m_Attributes.emplace_back(xmb.GetAttributeName(std::string(name)), attrVal); + } + return true; +} + +template<> +bool XMBStorageWriter::OutputElements(WriteBuffer& writeBuffer, xmlNodePtr&& node) +{ + // Filled in later with the length of the element + size_t posLength = writeBuffer.Size(); + writeBuffer.Append("????", 4); + + u32 name = GetElementName((const char*)node->name); + writeBuffer.Append(&name, 4); + + u32 attrCount = 0; + for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) + ++attrCount; + writeBuffer.Append(&attrCount, 4); + + u32 childCount = 0; + for (xmlNodePtr child = node->children; child; child = child->next) + if (child->type == XML_ELEMENT_NODE) + ++childCount; + writeBuffer.Append(&childCount, 4); + + // Filled in later with the offset to the list of child elements + size_t posChildrenOffset = writeBuffer.Size(); + writeBuffer.Append("????", 4); + + + // Trim excess whitespace in the entity's text, while counting + // the number of newlines trimmed (so that JS error reporting + // can give the correct line number within the script) + + std::string whitespace = " \t\r\n"; + std::string text; + for (xmlNodePtr child = node->children; child; child = child->next) + { + if (child->type == XML_TEXT_NODE) + { + xmlChar* content = xmlNodeGetContent(child); + text += std::string((const char*)content); + xmlFree(content); + } + } + + u32 linenum = xmlGetLineNo(node); + + // Find the start of the non-whitespace section + size_t first = text.find_first_not_of(whitespace); + + if (first == text.npos) + // Entirely whitespace - easy to handle + text = ""; + + else + { + // Count the number of \n being cut off, + // and add them to the line number + std::string trimmed (text.begin(), text.begin()+first); + linenum += std::count(trimmed.begin(), trimmed.end(), '\n'); + + // Find the end of the non-whitespace section, + // and trim off everything else + size_t last = text.find_last_not_of(whitespace); + text = text.substr(first, 1+last-first); + } + + + // Output text, prefixed by length in bytes + if (text.length() == 0) + { + // No text; don't write much + writeBuffer.Append("\0\0\0\0", 4); + } + else + { + // Write length and line number and null-terminated text + u32 nodeLen = u32(4 + text.length()+1); + writeBuffer.Append(&nodeLen, 4); + writeBuffer.Append(&linenum, 4); + writeBuffer.Append((void*)text.c_str(), nodeLen-4); + } + + // Output attributes + for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) + { + u32 attrName = GetAttributeName((const char*)attr->name); + writeBuffer.Append(&attrName, 4); + + xmlChar* value = xmlNodeGetContent(attr->children); + u32 attrLen = u32(xmlStrlen(value)+1); + writeBuffer.Append(&attrLen, 4); + writeBuffer.Append((void*)value, attrLen); + xmlFree(value); + } + + // Go back and fill in the child-element offset + u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4)); + writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset); + + // Output all child elements + for (xmlNodePtr child = node->children; child; child = child->next) + if (child->type == XML_ELEMENT_NODE) + OutputElements(writeBuffer, std::move(child)); + + // Go back and fill in the length + u32 length = (u32)(writeBuffer.Size() - posLength); + writeBuffer.Overwrite(&length, 4, posLength); + + return true; +} +} // anonymous namespace + +bool XMBStorage::ReadFromFile(const PIVFS& vfs, const VfsPath& filename) +{ + if(vfs->LoadFile(filename, m_Buffer, m_Size) < 0) + return false; + // if the game crashes during loading, (e.g. due to driver bugs), + // it sometimes leaves empty XMB files in the cache. + // reporting failure will cause our caller to re-generate the XMB. + if (m_Size == 0) + return false; + ENSURE(m_Size >= 4); // make sure it's at least got the initial header + return true; +} + +bool XMBStorage::LoadXMLDoc(const xmlDocPtr doc) +{ + WriteBuffer writeBuffer; + + XMBStorageWriter writer; + if (!writer.Load(writeBuffer, std::move(xmlDocGetRootElement(doc)))) + return false; + + m_Buffer = writeBuffer.Data(); // add a reference + m_Size = writeBuffer.Size(); + return true; +} + +bool XMBStorage::LoadJSValue(const ScriptInterface& scriptInterface, JS::HandleValue value, const std::string& rootName) +{ + WriteBuffer writeBuffer; + + XMBStorageWriter writer; + const u32 name = writer.GetElementName(rootName); + JSNodeData data(scriptInterface); + if (!writer.Load(writeBuffer, data, name, std::move(value))) + return false; + + m_Buffer = writeBuffer.Data(); // add a reference + m_Size = writeBuffer.Size(); + return true; +} Index: source/ps/XML/XMLWriter.h =================================================================== --- source/ps/XML/XMLWriter.h +++ source/ps/XML/XMLWriter.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -65,7 +65,7 @@ #include "ps/CStr.h" class XMBElement; -class XMBFile; +class XMBData; class XMLWriter_Element; class XMLWriter_File @@ -77,7 +77,7 @@ void Comment(const char* text); - void XMB(const XMBFile& file); + void XMB(const XMBData& file); bool StoreVFS(const PIVFS& vfs, const VfsPath& pathname); const CStr8& GetOutput(); @@ -86,7 +86,7 @@ friend class XMLWriter_Element; - void ElementXMB(const XMBFile& file, XMBElement el); + void ElementXMB(const XMBData& file, XMBElement el); void ElementStart(XMLWriter_Element* element, const char* name); void ElementText(const char* text, bool cdata); Index: source/ps/XML/XMLWriter.cpp =================================================================== --- source/ps/XML/XMLWriter.cpp +++ source/ps/XML/XMLWriter.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -114,17 +114,17 @@ } -void XMLWriter_File::XMB(const XMBFile& file) +void XMLWriter_File::XMB(const XMBData& file) { ElementXMB(file, file.GetRoot()); } -void XMLWriter_File::ElementXMB(const XMBFile& file, XMBElement el) +void XMLWriter_File::ElementXMB(const XMBData& file, XMBElement el) { - XMLWriter_Element writer(*this, file.GetElementString(el.GetNodeName()).c_str()); + XMLWriter_Element writer(*this, file.GetElementString(el.GetNodeName())); XERO_ITER_ATTR(el, attr) - writer.Attribute(file.GetAttributeString(attr.Name).c_str(), attr.Value); + writer.Attribute(file.GetAttributeString(attr.Name), attr.Value); XERO_ITER_EL(el, child) ElementXMB(file, child); Index: source/ps/XML/Xeromyces.h =================================================================== --- source/ps/XML/Xeromyces.h +++ source/ps/XML/Xeromyces.h @@ -30,19 +30,17 @@ ERROR_TYPE(Xeromyces, XMLParseError); ERROR_TYPE(Xeromyces, XMLValidationFailed); -#include "XeroXMB.h" +#include "ps/XMB/XMBData.h" +#include "ps/XMB/XMBStorage.h" #include "lib/file/vfs/vfs.h" class RelaxNGValidator; -class WriteBuffer; -typedef struct _xmlDoc xmlDoc; -typedef xmlDoc* xmlDocPtr; - -class CXeromyces : public XMBFile +class CXeromyces : public XMBData { friend class TestXeroXMB; + friend class XMBData; public: /** * Load from an XML file (with invisible XMB caching). @@ -81,11 +79,7 @@ PSRETURN ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath, const std::string& validatorName); - bool ReadXMBFile(const PIVFS& vfs, const VfsPath& filename); - - static PSRETURN CreateXMB(const xmlDocPtr doc, WriteBuffer& writeBuffer); - - shared_ptr m_XMBBuffer; + XMBStorage m_Data; }; Index: source/ps/XML/Xeromyces.cpp =================================================================== --- source/ps/XML/Xeromyces.cpp +++ source/ps/XML/Xeromyces.cpp @@ -28,6 +28,7 @@ #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" + #include "RelaxNG.h" #include "Xeromyces.h" @@ -120,13 +121,17 @@ validatorGrammarHash = GetValidator(validatorName).GetGrammarHash(); } VfsPath xmbPath; - Status ret = cacheLoader.TryLoadingCached(filename, validatorGrammarHash, XMBVersion, xmbPath); + Status ret = cacheLoader.TryLoadingCached(filename, validatorGrammarHash, XMBStorage::XMBVersion, xmbPath); if (ret == INFO::OK) { // Found a cached XMB - load it - if (ReadXMBFile(vfs, xmbPath)) + if (m_Data.ReadFromFile(vfs, xmbPath)) + { + if(!Initialise(m_Data)) + return PSRETURN_Xeromyces_XMLParseError; return PSRETURN_OK; + } // If this fails then we'll continue and (re)create the loose cache - // this failure legitimately happens due to partially-written XMB files. } @@ -184,43 +189,20 @@ } } - WriteBuffer writeBuffer; - CreateXMB(doc, writeBuffer); - + m_Data.LoadXMLDoc(doc); xmlFreeDoc(doc); // Save the file to disk, so it can be loaded quickly next time. // Don't save if invalid, because we want the syntax error every program start. - vfs->CreateFile(xmbPath, writeBuffer.Data(), writeBuffer.Size()); + vfs->CreateFile(xmbPath, m_Data.m_Buffer, m_Data.m_Size); - m_XMBBuffer = writeBuffer.Data(); // add a reference - - // Set up the XMBFile - const bool ok = Initialise((const char*)m_XMBBuffer.get()); + // Set up the XMBData + const bool ok = Initialise(m_Data); ENSURE(ok); return PSRETURN_OK; } -bool CXeromyces::ReadXMBFile(const PIVFS& vfs, const VfsPath& filename) -{ - size_t size; - if(vfs->LoadFile(filename, m_XMBBuffer, size) < 0) - return false; - // if the game crashes during loading, (e.g. due to driver bugs), - // it sometimes leaves empty XMB files in the cache. - // reporting failure will cause our caller to re-generate the XMB. - if(size == 0) - return false; - ENSURE(size >= 4); // make sure it's at least got the initial header - - // Set up the XMBFile - if(!Initialise((const char*)m_XMBBuffer.get())) - return false; - - return true; -} - PSRETURN CXeromyces::LoadString(const char* xml, const std::string& validatorName /* = "" */) { ENSURE(g_XeromycesStarted); @@ -242,185 +224,12 @@ } } - WriteBuffer writeBuffer; - CreateXMB(doc, writeBuffer); - + m_Data.LoadXMLDoc(doc); xmlFreeDoc(doc); - m_XMBBuffer = writeBuffer.Data(); // add a reference - - // Set up the XMBFile - const bool ok = Initialise((const char*)m_XMBBuffer.get()); + // Set up the XMBData + const bool ok = Initialise(m_Data); ENSURE(ok); return PSRETURN_OK; } - - -static void FindNames(const xmlNodePtr node, std::set& elementNames, std::set& attributeNames) -{ - elementNames.insert((const char*)node->name); - - for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) - attributeNames.insert((const char*)attr->name); - - for (xmlNodePtr child = node->children; child; child = child->next) - if (child->type == XML_ELEMENT_NODE) - FindNames(child, elementNames, attributeNames); -} - -static void OutputElement(const xmlNodePtr node, WriteBuffer& writeBuffer, - std::map& elementIDs, - std::map& attributeIDs -) -{ - // Filled in later with the length of the element - size_t posLength = writeBuffer.Size(); - writeBuffer.Append("????", 4); - - writeBuffer.Append(&elementIDs[(const char*)node->name], 4); - - u32 attrCount = 0; - for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) - ++attrCount; - writeBuffer.Append(&attrCount, 4); - - u32 childCount = 0; - for (xmlNodePtr child = node->children; child; child = child->next) - if (child->type == XML_ELEMENT_NODE) - ++childCount; - writeBuffer.Append(&childCount, 4); - - // Filled in later with the offset to the list of child elements - size_t posChildrenOffset = writeBuffer.Size(); - writeBuffer.Append("????", 4); - - - // Trim excess whitespace in the entity's text, while counting - // the number of newlines trimmed (so that JS error reporting - // can give the correct line number within the script) - - std::string whitespace = " \t\r\n"; - std::string text; - for (xmlNodePtr child = node->children; child; child = child->next) - { - if (child->type == XML_TEXT_NODE) - { - xmlChar* content = xmlNodeGetContent(child); - text += std::string((const char*)content); - xmlFree(content); - } - } - - u32 linenum = xmlGetLineNo(node); - - // Find the start of the non-whitespace section - size_t first = text.find_first_not_of(whitespace); - - if (first == text.npos) - // Entirely whitespace - easy to handle - text = ""; - - else - { - // Count the number of \n being cut off, - // and add them to the line number - std::string trimmed (text.begin(), text.begin()+first); - linenum += std::count(trimmed.begin(), trimmed.end(), '\n'); - - // Find the end of the non-whitespace section, - // and trim off everything else - size_t last = text.find_last_not_of(whitespace); - text = text.substr(first, 1+last-first); - } - - - // Output text, prefixed by length in bytes - if (text.length() == 0) - { - // No text; don't write much - writeBuffer.Append("\0\0\0\0", 4); - } - else - { - // Write length and line number and null-terminated text - u32 nodeLen = u32(4 + text.length()+1); - writeBuffer.Append(&nodeLen, 4); - writeBuffer.Append(&linenum, 4); - writeBuffer.Append((void*)text.c_str(), nodeLen-4); - } - - // Output attributes - for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) - { - writeBuffer.Append(&attributeIDs[(const char*)attr->name], 4); - - xmlChar* value = xmlNodeGetContent(attr->children); - u32 attrLen = u32(xmlStrlen(value)+1); - writeBuffer.Append(&attrLen, 4); - writeBuffer.Append((void*)value, attrLen); - xmlFree(value); - } - - // Go back and fill in the child-element offset - u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4)); - writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset); - - // Output all child elements - for (xmlNodePtr child = node->children; child; child = child->next) - if (child->type == XML_ELEMENT_NODE) - OutputElement(child, writeBuffer, elementIDs, attributeIDs); - - // Go back and fill in the length - u32 length = (u32)(writeBuffer.Size() - posLength); - writeBuffer.Overwrite(&length, 4, posLength); -} - -PSRETURN CXeromyces::CreateXMB(const xmlDocPtr doc, WriteBuffer& writeBuffer) -{ - // Header - writeBuffer.Append(UnfinishedHeaderMagicStr, 4); - // Version - writeBuffer.Append(&XMBVersion, 4); - - u32 i; - - // Find the unique element/attribute names - std::set elementNames; - std::set attributeNames; - FindNames(xmlDocGetRootElement(doc), elementNames, attributeNames); - - std::map elementIDs; - std::map attributeIDs; - - // Output element names - i = 0; - u32 elementCount = (u32)elementNames.size(); - writeBuffer.Append(&elementCount, 4); - for (const std::string& n : elementNames) - { - u32 textLen = (u32)n.length()+1; - writeBuffer.Append(&textLen, 4); - writeBuffer.Append((void*)n.c_str(), textLen); - elementIDs[n] = i++; - } - - // Output attribute names - i = 0; - u32 attributeCount = (u32)attributeNames.size(); - writeBuffer.Append(&attributeCount, 4); - for (const std::string& n : attributeNames) - { - u32 textLen = (u32)n.length()+1; - writeBuffer.Append(&textLen, 4); - writeBuffer.Append((void*)n.c_str(), textLen); - attributeIDs[n] = i++; - } - - OutputElement(xmlDocGetRootElement(doc), writeBuffer, elementIDs, attributeIDs); - - // file is now valid, so insert correct magic string - writeBuffer.Overwrite(HeaderMagicStr, 4, 0); - - return PSRETURN_OK; -} Index: source/ps/XML/tests/test_XeroXMB.h =================================================================== --- source/ps/XML/tests/test_XeroXMB.h +++ source/ps/XML/tests/test_XeroXMB.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -19,8 +19,6 @@ #include "ps/XML/Xeromyces.h" -#include "lib/file/io/write_buffer.h" - #include class TestXeroXMB : public CxxTest::TestSuite @@ -28,19 +26,18 @@ private: shared_ptr m_Buffer; - XMBFile parse(const char* doc) + XMBData parse(const char* doc) { xmlDocPtr xmlDoc = xmlReadMemory(doc, int(strlen(doc)), "", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); - WriteBuffer buffer; - PSRETURN ret = CXeromyces::CreateXMB(xmlDoc, buffer); + XMBStorage storage; + bool ok = storage.LoadXMLDoc(xmlDoc); xmlFreeDoc(xmlDoc); - TS_ASSERT_EQUALS(ret, PSRETURN_OK); + TS_ASSERT_EQUALS(ok, true); - XMBFile xmb; - m_Buffer = buffer.Data(); // hold a reference - TS_ASSERT(xmb.Initialise((const char*)m_Buffer.get())); + XMBData xmb; + TS_ASSERT(xmb.Initialise(storage)); return xmb; } @@ -52,7 +49,7 @@ public: void test_basic() { - XMBFile xmb (parse("\n bar \n\n\nbar\n")); + XMBData xmb (parse("\n bar \n\n\nbar\n")); TS_ASSERT_DIFFERS(xmb.GetElementID("test"), -1); TS_ASSERT_DIFFERS(xmb.GetElementID("foo"), -1); @@ -88,7 +85,7 @@ void test_GetFirstNamedItem() { - XMBFile xmb (parse(" A B C D ")); + XMBData xmb (parse(" A B C D ")); XMBElement root = xmb.GetRoot(); TS_ASSERT_EQUALS(root.GetChildNodes().size(), 4); @@ -112,20 +109,20 @@ void test_doctype_ignored() { - XMBFile xmb (parse("")); + XMBData xmb (parse("")); TS_ASSERT_DIFFERS(xmb.GetElementID("foo"), -1); } void test_complex_parse() { - XMBFile xmb (parse("\t\n \tx <>&"'bar\n\nbazqux")); + XMBData xmb (parse("\t\n \tx <>&"'bar\n\nbazqux")); TS_ASSERT_EQUALS(CStr(xmb.GetRoot().GetText()), "x <>&\"'foobar\n\nbazqux"); } void test_unicode() { - XMBFile xmb (parse("ሴ\xE1\x88\xB4")); + XMBData xmb (parse("ሴ\xE1\x88\xB4")); CStrW text; text = xmb.GetRoot().GetText().FromUTF8(); @@ -141,7 +138,7 @@ void test_iso88591() { - XMBFile xmb (parse("ሴ\xE1\x88\xB4")); + XMBData xmb (parse("ሴ\xE1\x88\xB4")); CStrW text; text = xmb.GetRoot().GetText().FromUTF8(); Index: source/simulation2/system/ParamNode.h =================================================================== --- source/simulation2/system/ParamNode.h +++ source/simulation2/system/ParamNode.h @@ -27,7 +27,7 @@ #include #include -class XMBFile; +class XMBData; class XMBElement; class ScriptRequest; @@ -165,7 +165,7 @@ * @param sourceIdentifier Optional; string you can pass along to indicate the source of * the data getting loaded. Used for output to log messages if an error occurs. */ - static void LoadXML(CParamNode& ret, const XMBFile& file, const wchar_t* sourceIdentifier = NULL); + static void LoadXML(CParamNode& ret, const XMBData& file, const wchar_t* sourceIdentifier = NULL); /** * Loads the XML data specified by @a path into the node @a ret. @@ -276,7 +276,7 @@ * @param sourceIdentifier Optional; string you can pass along to indicate the source of * the data getting applied. Used for output to log messages if an error occurs. */ - void ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier = NULL); + void ApplyLayer(const XMBData& xmb, const XMBElement& element, const wchar_t* sourceIdentifier = NULL); void ResetScriptVal(); Index: source/simulation2/system/ParamNode.cpp =================================================================== --- source/simulation2/system/ParamNode.cpp +++ source/simulation2/system/ParamNode.cpp @@ -37,7 +37,7 @@ { } -void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb, const wchar_t* sourceIdentifier /*= NULL*/) +void CParamNode::LoadXML(CParamNode& ret, const XMBData& xmb, const wchar_t* sourceIdentifier /*= NULL*/) { ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier); } @@ -64,11 +64,11 @@ return PSRETURN_OK; } -void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/) +void CParamNode::ApplyLayer(const XMBData& xmb, const XMBElement& element, const wchar_t* sourceIdentifier /*= NULL*/) { ResetScriptVal(); - std::string name = xmb.GetElementString(element.GetNodeName()); // TODO: is GetElementString inefficient? + std::string name = xmb.GetElementString(element.GetNodeName()); CStr value = element.GetText(); bool hasSetValue = false; @@ -224,8 +224,8 @@ if (attr.Name == at_replace || attr.Name == at_op || attr.Name == at_merge || attr.Name == at_filtered) continue; // Add any others - std::string attrName = xmb.GetAttributeString(attr.Name); - node.m_Childs["@" + attrName].m_Value = attr.Value; + const char* attrName(xmb.GetAttributeString(attr.Name)); + node.m_Childs[CStr("@") + attrName].m_Value = attr.Value; } } Index: source/soundmanager/scripting/SoundGroup.cpp =================================================================== --- source/soundmanager/scripting/SoundGroup.cpp +++ source/soundmanager/scripting/SoundGroup.cpp @@ -329,7 +329,7 @@ if (root.GetNodeName() != el_soundgroup) { - LOGERROR("Invalid SoundGroup format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()).c_str()); + LOGERROR("Invalid SoundGroup format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName())); return false; }