diff --git a/source/graphics/MapReader.cpp b/source/graphics/MapReader.cpp index dbf5429f4e..22e2a76c83 100644 --- a/source/graphics/MapReader.cpp +++ b/source/graphics/MapReader.cpp @@ -358,7 +358,7 @@ PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname) VfsPath filename_xml = pathname.ChangeExtension(L".xml"); CXeromyces xmb_file; - if (xmb_file.Load(g_VFS, filename_xml) != PSRETURN_OK) + if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK) return PSRETURN_File_ReadFailed; // Define all the relevant elements used in the XML file @@ -467,7 +467,7 @@ void CXMLReader::Init(const VfsPath& xml_filename) // must only assign once, so do it here node_idx = entity_idx = 0; - if (xmb_file.Load(g_VFS, xml_filename) != PSRETURN_OK) + if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK) throw PSERROR_File_ReadFailed(); // define the elements and attributes that are frequently used in the XML file, diff --git a/source/graphics/MaterialManager.cpp b/source/graphics/MaterialManager.cpp index 341fb3ba8c..e0eaa2cede 100644 --- a/source/graphics/MaterialManager.cpp +++ b/source/graphics/MaterialManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -22,6 +22,7 @@ #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Vector4D.h" +#include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/PreprocessorWrapper.h" @@ -35,6 +36,9 @@ CMaterialManager::CMaterialManager() qualityLevel = 5.0; CFG_GET_VAL("materialmgr.quality", qualityLevel); qualityLevel = clamp(qualityLevel, 0.0f, 10.0f); + + if (!CXeromyces::AddValidator(g_VFS, "material", "art/materials/material.rng")) + LOGERROR("CMaterialManager: failed to load grammar file 'art/materials/material.rng'"); } CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) @@ -47,7 +51,7 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) return iter->second; CXeromyces xeroFile; - if (xeroFile.Load(g_VFS, pathname) != PSRETURN_OK) + if (xeroFile.Load(g_VFS, pathname, "material") != PSRETURN_OK) return CMaterial(); #define EL(x) int el_##x = xeroFile.GetElementID(#x) diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index 2ab9217e46..27a816a831 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -44,7 +44,7 @@ bool CObjectBase::Load(const VfsPath& pathname) m_UsedFiles.insert(pathname); CXeromyces XeroFile; - if (XeroFile.Load(g_VFS, pathname) != PSRETURN_OK) + if (XeroFile.Load(g_VFS, pathname, "actor") != PSRETURN_OK) return false; // Define all the elements used in the XML file diff --git a/source/graphics/ObjectManager.cpp b/source/graphics/ObjectManager.cpp index 9d23072335..8b0728c7cc 100644 --- a/source/graphics/ObjectManager.cpp +++ b/source/graphics/ObjectManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -25,6 +25,7 @@ #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Filesystem.h" +#include "ps/XML/Xeromyces.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpVisual.h" @@ -62,6 +63,9 @@ CObjectManager::CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& : m_MeshManager(meshManager), m_SkeletonAnimManager(skeletonAnimManager), m_Simulation(simulation) { RegisterFileReloadFunc(ReloadChangedFileCB, this); + + if (!CXeromyces::AddValidator(g_VFS, "actor", "art/actors/actor.rng")) + LOGERROR("CObjectManager: failed to load actor grammar file 'art/actors/actor.rng'"); } CObjectManager::~CObjectManager() diff --git a/source/graphics/ParticleEmitterType.cpp b/source/graphics/ParticleEmitterType.cpp index 46cc663dea..1479c2b2aa 100644 --- a/source/graphics/ParticleEmitterType.cpp +++ b/source/graphics/ParticleEmitterType.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -361,12 +361,10 @@ bool CParticleEmitterType::LoadXML(const VfsPath& path) m_Texture = g_Renderer.GetTextureManager().GetErrorTexture(); CXeromyces XeroFile; - PSRETURN ret = XeroFile.Load(g_VFS, path); + PSRETURN ret = XeroFile.Load(g_VFS, path, "particle"); if (ret != PSRETURN_OK) return false; - // TODO: should do some RNG schema validation - // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) diff --git a/source/graphics/ShaderManager.cpp b/source/graphics/ShaderManager.cpp index bbea4297e2..ccd4e3bd83 100644 --- a/source/graphics/ShaderManager.cpp +++ b/source/graphics/ShaderManager.cpp @@ -28,6 +28,9 @@ #include "ps/Filesystem.h" #include "ps/PreprocessorWrapper.h" #include "ps/Profile.h" +#if USE_SHADER_XML_VALIDATION +# include "ps/XML/RelaxNG.h" +#endif #include "ps/XML/Xeromyces.h" #include "ps/XML/XMLWriter.h" #include "renderer/Renderer.h" @@ -47,14 +50,9 @@ CShaderManager::CShaderManager() #if USE_SHADER_XML_VALIDATION { TIMER_ACCRUE(tc_ShaderValidation); - CVFSFile grammar; - if (grammar.Load(g_VFS, L"shaders/program.rng") != PSRETURN_OK) - LOGERROR("Failed to read grammar shaders/program.rng"); - else - { - if (!m_Validator.LoadGrammar(grammar.GetAsString())) - LOGERROR("Failed to load grammar shaders/program.rng"); - } + + if (!CXeromyces::AddValidator(g_VFS, "shader", "shaders/program.rng")) + LOGERROR("CShaderManager: failed to load grammar shaders/program.rng"); } #endif @@ -141,7 +139,7 @@ bool CShaderManager::NewProgram(const char* name, const CShaderDefines& baseDefi XML_Start(); XML_SetPrettyPrint(false); XML_WriteXMB(XeroFile); - bool ok = m_Validator.ValidateEncoded(wstring_from_utf8(name), XML_GetOutput()); + bool ok = CXeromyces::ValidateEncoded("shader", wstring_from_utf8(name), XML_GetOutput()); if (!ok) return false; } diff --git a/source/graphics/ShaderManager.h b/source/graphics/ShaderManager.h index 5cc117d3ea..ca0044cd0e 100644 --- a/source/graphics/ShaderManager.h +++ b/source/graphics/ShaderManager.h @@ -121,10 +121,6 @@ private: typedef boost::unordered_map, std::owner_less>>> HotloadFilesMap; HotloadFilesMap m_HotloadFiles; -#if USE_SHADER_XML_VALIDATION - RelaxNGValidator m_Validator; -#endif - bool NewProgram(const char* name, const CShaderDefines& defines, CShaderProgramPtr& program); bool NewEffect(const char* name, const CShaderDefines& defines, CShaderTechniquePtr& tech); diff --git a/source/graphics/TerrainProperties.cpp b/source/graphics/TerrainProperties.cpp index c1c41600ef..fae31e0832 100644 --- a/source/graphics/TerrainProperties.cpp +++ b/source/graphics/TerrainProperties.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -46,7 +46,7 @@ CTerrainProperties::CTerrainProperties(CTerrainPropertiesPtr parent): CTerrainPropertiesPtr CTerrainProperties::FromXML(const CTerrainPropertiesPtr& parent, const VfsPath& pathname) { CXeromyces XeroFile; - if (XeroFile.Load(g_VFS, pathname) != PSRETURN_OK) + if (XeroFile.Load(g_VFS, pathname, "terrain") != PSRETURN_OK) return CTerrainPropertiesPtr(); XMBElement root = XeroFile.GetRoot(); diff --git a/source/graphics/TerrainTextureEntry.cpp b/source/graphics/TerrainTextureEntry.cpp index 3e785bc54f..677c126239 100644 --- a/source/graphics/TerrainTextureEntry.cpp +++ b/source/graphics/TerrainTextureEntry.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -45,7 +45,7 @@ CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, con ENSURE(properties); CXeromyces XeroFile; - if (XeroFile.Load(g_VFS, path) != PSRETURN_OK) + if (XeroFile.Load(g_VFS, path, "terrain_texture") != PSRETURN_OK) { LOGERROR("Terrain xml not found (%s)", path.string8()); return; diff --git a/source/graphics/TerrainTextureManager.cpp b/source/graphics/TerrainTextureManager.cpp index f9e96962ca..cb244341d8 100644 --- a/source/graphics/TerrainTextureManager.cpp +++ b/source/graphics/TerrainTextureManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -30,13 +30,19 @@ #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/XML/Xeromyces.h" #include CTerrainTextureManager::CTerrainTextureManager(): m_LastGroupIndex(0) -{} +{ + if (!CXeromyces::AddValidator(g_VFS, "terrain", "art/terrains/terrain.rng")) + LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain.rng'"); + if (!CXeromyces::AddValidator(g_VFS, "terrain_texture", "art/terrains/terrain_texture.rng")) + LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain_texture.rng'"); +} CTerrainTextureManager::~CTerrainTextureManager() { diff --git a/source/graphics/TextureConverter.cpp b/source/graphics/TextureConverter.cpp index d4c7eea65c..05392da6ee 100644 --- a/source/graphics/TextureConverter.cpp +++ b/source/graphics/TextureConverter.cpp @@ -96,7 +96,7 @@ void CTextureConverter::Settings::Hash(MD5& hash) CTextureConverter::SettingsFile* CTextureConverter::LoadSettings(const VfsPath& path) const { CXeromyces XeroFile; - if (XeroFile.Load(m_VFS, path) != PSRETURN_OK) + if (XeroFile.Load(m_VFS, path, "texture") != PSRETURN_OK) return NULL; // Define all the elements used in the XML file diff --git a/source/gui/CGUI.cpp b/source/gui/CGUI.cpp index be2e2970a4..262e4a1505 100644 --- a/source/gui/CGUI.cpp +++ b/source/gui/CGUI.cpp @@ -929,7 +929,7 @@ void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set& P Paths.insert(Filename); CXeromyces XeroFile; - if (XeroFile.Load(g_VFS, Filename) != PSRETURN_OK) + if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK) // Fail silently return; @@ -1315,7 +1315,7 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec Paths.insert(filename); CXeromyces XeroIncluded; - if (XeroIncluded.Load(g_VFS, filename) != PSRETURN_OK) + if (XeroIncluded.Load(g_VFS, filename, "gui") != PSRETURN_OK) { LOGERROR("GUI: Error reading included XML: '%s'", utf8_from_wstring(filename)); continue; @@ -1352,7 +1352,7 @@ void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec // one might use the same parts of the GUI in different situations Paths.insert(path); CXeromyces XeroIncluded; - if (XeroIncluded.Load(g_VFS, path) != PSRETURN_OK) + if (XeroIncluded.Load(g_VFS, path, "gui") != PSRETURN_OK) { LOGERROR("GUI: Error reading included XML: '%s'", path.string8()); continue; diff --git a/source/gui/GUIManager.cpp b/source/gui/GUIManager.cpp index de3260f940..07f74f9998 100644 --- a/source/gui/GUIManager.cpp +++ b/source/gui/GUIManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -59,6 +59,12 @@ CGUIManager::CGUIManager() m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIManager", m_ScriptRuntime)); m_ScriptInterface->SetCallbackData(this); m_ScriptInterface->LoadGlobalScripts(); + + if (!CXeromyces::AddValidator(g_VFS, "gui_page", "gui/gui_page.rng")) + LOGERROR("CGUIManager: failed to load GUI page grammar file 'gui/gui_page.rng'"); + if (!CXeromyces::AddValidator(g_VFS, "gui", "gui/gui.rng")) + LOGERROR("CGUIManager: failed to load GUI XML grammar file 'gui/gui.rng'"); + RegisterFileReloadFunc(ReloadChangedFileCB, this); } @@ -192,7 +198,7 @@ void CGUIManager::LoadPage(SGUIPage& page) page.inputs.insert(path); CXeromyces xero; - if (xero.Load(g_VFS, path) != PSRETURN_OK) + if (xero.Load(g_VFS, path, "gui_page") != PSRETURN_OK) // Fail silently (Xeromyces reported the error) return; diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp index 3ff55051e1..a60d24794f 100644 --- a/source/gui/MiniMap.cpp +++ b/source/gui/MiniMap.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -38,6 +38,7 @@ #include "ps/Game.h" #include "ps/Profile.h" #include "ps/World.h" +#include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" @@ -70,9 +71,12 @@ CMiniMap::CMiniMap() : m_Clicking = false; m_MouseHovering = false; + // Register Relax NG validator + CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); + // Get the maximum height for unit passage in water. CParamNode externalParamNode; - CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml"); + CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index b276fef4a5..13ccd4c48a 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -1074,6 +1074,9 @@ void InitGraphics(const CmdLineArgs& args, int flags) ogl_WarnIfError(); + // TODO: Is this the best place for this? + CXeromyces::AddValidator(g_VFS, "map", "maps/scenario.rng"); + try { if (!VisualReplay(args.Get("replay-visual")) && !Autostart(args)) diff --git a/source/ps/XML/RelaxNG.cpp b/source/ps/XML/RelaxNG.cpp index 57d63449e4..fedb4c3507 100644 --- a/source/ps/XML/RelaxNG.cpp +++ b/source/ps/XML/RelaxNG.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -22,6 +22,7 @@ #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" +#include "ps/Filesystem.h" #include #include @@ -33,6 +34,27 @@ TIMER_ADD_CLIENT(xml_validation); * To minimise that problem, keep a global cache of parsed schemas, so we don't * leak an indefinitely large amount of memory when repeatedly restarting the simulation. */ +class RelaxNGSchema; +static std::map > g_SchemaCache; +static CMutex g_SchemaCacheLock; + +void ClearSchemaCache() +{ + CScopeLock lock(g_SchemaCacheLock); + g_SchemaCache.clear(); +} + +static void relaxNGErrorHandler(void* UNUSED(userData), xmlErrorPtr error) +{ + // Strip a trailing newline + std::string message = error->message; + if (message.length() > 0 && message[message.length()-1] == '\n') + message.erase(message.length()-1); + + LOGERROR("RelaxNGValidator: Validation %s: %s:%d: %s", + error->level == XML_ERR_WARNING ? "warning" : "error", + error->file, error->line, message.c_str()); +} class RelaxNGSchema { @@ -56,9 +78,6 @@ public: } }; -static std::map > g_SchemaCache; -static CMutex g_SchemaCacheLock; - RelaxNGValidator::RelaxNGValidator() : m_Schema(NULL) { @@ -70,8 +89,6 @@ RelaxNGValidator::~RelaxNGValidator() bool RelaxNGValidator::LoadGrammar(const std::string& grammar) { - TIMER_ACCRUE(xml_validation); - shared_ptr schema; { @@ -89,20 +106,33 @@ bool RelaxNGValidator::LoadGrammar(const std::string& grammar) } m_Schema = schema->m_Schema; - if (!m_Schema) return false; + + MD5 hash; + hash.Update((const u8*)grammar.c_str(), grammar.length()); + m_Hash = hash; + return true; } -bool RelaxNGValidator::Validate(const std::wstring& filename, const std::wstring& document) +bool RelaxNGValidator::LoadGrammarFile(const PIVFS& vfs, const VfsPath& grammarPath) +{ + CVFSFile file; + if (file.Load(vfs, grammarPath) != PSRETURN_OK) + return false; + + return LoadGrammar(file.DecodeUTF8()); +} + +bool RelaxNGValidator::Validate(const std::wstring& filename, const std::wstring& document) const { std::string docutf8 = "" + utf8_from_wstring(document); return ValidateEncoded(filename, docutf8); } -bool RelaxNGValidator::ValidateEncoded(const std::wstring& filename, const std::string& document) +bool RelaxNGValidator::ValidateEncoded(const std::wstring& filename, const std::string& document) const { TIMER_ACCRUE(xml_validation); @@ -115,14 +145,21 @@ bool RelaxNGValidator::ValidateEncoded(const std::wstring& filename, const std:: xmlDocPtr doc = xmlReadMemory(document.c_str(), (int)document.size(), utf8_from_wstring(filename).c_str(), NULL, XML_PARSE_NONET); if (doc == NULL) { - LOGERROR("RelaxNGValidator: Failed to parse document"); + LOGERROR("RelaxNGValidator: Failed to parse document '%s'", utf8_from_wstring(filename).c_str()); return false; } + bool ret = ValidateEncoded(doc); + xmlFreeDoc(doc); + return ret; +} + +bool RelaxNGValidator::ValidateEncoded(xmlDocPtr doc) const +{ xmlRelaxNGValidCtxtPtr ctxt = xmlRelaxNGNewValidCtxt(m_Schema); + xmlRelaxNGSetValidStructuredErrors(ctxt, &relaxNGErrorHandler, NULL); int ret = xmlRelaxNGValidateDoc(ctxt, doc); xmlRelaxNGFreeValidCtxt(ctxt); - xmlFreeDoc(doc); if (ret == 0) { @@ -130,7 +167,7 @@ bool RelaxNGValidator::ValidateEncoded(const std::wstring& filename, const std:: } else if (ret > 0) { - LOGERROR("RelaxNGValidator: Validation failed"); + LOGERROR("RelaxNGValidator: Validation failed for '%s'", doc->name); return false; } else @@ -139,3 +176,8 @@ bool RelaxNGValidator::ValidateEncoded(const std::wstring& filename, const std:: return false; } } + +bool RelaxNGValidator::CanValidate() const +{ + return m_Schema != NULL; +} diff --git a/source/ps/XML/RelaxNG.h b/source/ps/XML/RelaxNG.h index 18758192b2..0bc80748f0 100644 --- a/source/ps/XML/RelaxNG.h +++ b/source/ps/XML/RelaxNG.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -18,8 +18,15 @@ #ifndef INCLUDED_RELAXNG #define INCLUDED_RELAXNG +#include "lib/file/vfs/vfs.h" +#include "maths/MD5.h" + typedef struct _xmlRelaxNG xmlRelaxNG; typedef xmlRelaxNG *xmlRelaxNGPtr; +typedef struct _xmlDoc xmlDoc; +typedef xmlDoc *xmlDocPtr; + +class IRelaxNGGrammar; class RelaxNGValidator { @@ -29,12 +36,26 @@ public: bool LoadGrammar(const std::string& grammar); - bool Validate(const std::wstring& filename, const std::wstring& document); + bool LoadGrammarFile(const PIVFS& vfs, const VfsPath& grammarPath); - bool ValidateEncoded(const std::wstring& filename, const std::string& document); + MD5 GetGrammarHash() const { return m_Hash; } + + bool Validate(const std::wstring& filename, const std::wstring& document) const; + + bool ValidateEncoded(const std::wstring& filename, const std::string& document) const; + + bool ValidateEncoded(xmlDocPtr doc) const; + + bool CanValidate() const; private: + MD5 m_Hash; xmlRelaxNGPtr m_Schema; }; +/** + * There should be no references to validators or schemas outside of the cache anymore when calling this. + */ +void ClearSchemaCache(); + #endif // INCLUDED_RELAXNG diff --git a/source/ps/XML/Xeromyces.cpp b/source/ps/XML/Xeromyces.cpp index 68ba0c6a03..89345b8e6e 100644 --- a/source/ps/XML/Xeromyces.cpp +++ b/source/ps/XML/Xeromyces.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,10 +27,15 @@ #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "RelaxNG.h" #include "Xeromyces.h" #include +static CMutex g_ValidatorCacheLock; +static std::map g_ValidatorCache; +static bool g_XeromycesStarted = false; + static void errorHandler(void* UNUSED(userData), xmlErrorPtr error) { // Strip a trailing newline @@ -45,31 +50,76 @@ static void errorHandler(void* UNUSED(userData), xmlErrorPtr error) // so the caching is less transparent than it should be } -static bool g_XeromycesStarted = false; void CXeromyces::Startup() { ENSURE(!g_XeromycesStarted); xmlInitParser(); xmlSetStructuredErrorFunc(NULL, &errorHandler); + CScopeLock lock(g_ValidatorCacheLock); + g_ValidatorCache.insert(std::make_pair("", RelaxNGValidator())); g_XeromycesStarted = true; } void CXeromyces::Terminate() { ENSURE(g_XeromycesStarted); - xmlCleanupParser(); - xmlSetStructuredErrorFunc(NULL, NULL); g_XeromycesStarted = false; + ClearSchemaCache(); + CScopeLock lock(g_ValidatorCacheLock); + g_ValidatorCache.clear(); + xmlSetStructuredErrorFunc(NULL, NULL); + xmlCleanupParser(); } -PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename) +bool CXeromyces::AddValidator(const PIVFS& vfs, const std::string& name, const VfsPath& grammarPath) +{ + ENSURE(g_XeromycesStarted); + + RelaxNGValidator validator; + if (!validator.LoadGrammarFile(vfs, grammarPath)) + { + LOGERROR("CXeromyces: failed adding validator for '%s'", grammarPath.string8()); + return false; + } + { + CScopeLock lock(g_ValidatorCacheLock); + std::map::iterator it = g_ValidatorCache.find(name); + if (it != g_ValidatorCache.end()) + g_ValidatorCache.erase(it); + g_ValidatorCache.insert(std::make_pair(name, validator)); + } + return true; +} + +bool CXeromyces::ValidateEncoded(const std::string& name, const std::wstring& filename, const std::string& document) +{ + CScopeLock lock(g_ValidatorCacheLock); + return GetValidator(name).ValidateEncoded(filename, document); +} + +/** + * NOTE: Callers MUST acquire the g_ValidatorCacheLock before calling this. + */ +RelaxNGValidator& CXeromyces::GetValidator(const std::string& name) +{ + if (g_ValidatorCache.find(name) == g_ValidatorCache.end()) + return g_ValidatorCache.find("")->second; + return g_ValidatorCache.find(name)->second; +} + +PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename, const std::string& validatorName /* = "" */) { ENSURE(g_XeromycesStarted); CCacheLoader cacheLoader(vfs, L".xmb"); + MD5 validatorGrammarHash; + { + CScopeLock lock(g_ValidatorCacheLock); + validatorGrammarHash = GetValidator(validatorName).GetGrammarHash(); + } VfsPath xmbPath; - Status ret = cacheLoader.TryLoadingCached(filename, MD5(), XMBVersion, xmbPath); + Status ret = cacheLoader.TryLoadingCached(filename, validatorGrammarHash, XMBVersion, xmbPath); if (ret == INFO::OK) { @@ -94,19 +144,19 @@ PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename) } // XMB isn't up to date with the XML, so rebuild it - return ConvertFile(vfs, filename, xmbPath); + return ConvertFile(vfs, filename, xmbPath, validatorName); } -bool CXeromyces::GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath) +bool CXeromyces::GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath, const std::string& validatorName /* = "" */) { CCacheLoader cacheLoader(vfs, L".xmb"); archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath); - return (ConvertFile(vfs, sourcePath, VfsPath("cache") / archiveCachePath) == PSRETURN_OK); + return (ConvertFile(vfs, sourcePath, VfsPath("cache") / archiveCachePath, validatorName) == PSRETURN_OK); } -PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath) +PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath, const std::string& validatorName) { CVFSFile input; if (input.Load(vfs, filename)) @@ -115,15 +165,22 @@ PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, cons return PSRETURN_Xeromyces_XMLOpenFailed; } - CStr8 filename8(CStrW(filename.string()).ToUTF8()); - xmlDocPtr doc = xmlReadMemory((const char*)input.GetBuffer(), (int)input.GetBufferSize(), - filename8.c_str(), NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); - if (! doc) + xmlDocPtr doc = xmlReadMemory((const char*)input.GetBuffer(), input.GetBufferSize(), CStrW(filename.string()).ToUTF8().c_str(), NULL, + XML_PARSE_NONET|XML_PARSE_NOCDATA); + if (!doc) { LOGERROR("CXeromyces: Failed to parse XML file %s", filename.string8()); return PSRETURN_Xeromyces_XMLParseError; } + { + CScopeLock lock(g_ValidatorCacheLock); + RelaxNGValidator& validator = GetValidator(validatorName); + if (validator.CanValidate() && !validator.ValidateEncoded(doc)) + // For now, log the error and continue, in the future we might fail + LOGERROR("CXeromyces: failed to validate XML file %s", filename.string8()); + } + WriteBuffer writeBuffer; CreateXMB(doc, writeBuffer); @@ -160,17 +217,25 @@ bool CXeromyces::ReadXMBFile(const PIVFS& vfs, const VfsPath& filename) return true; } -PSRETURN CXeromyces::LoadString(const char* xml) +PSRETURN CXeromyces::LoadString(const char* xml, const std::string& validatorName /* = "" */) { ENSURE(g_XeromycesStarted); - xmlDocPtr doc = xmlReadMemory(xml, (int)strlen(xml), "", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); - if (! doc) + xmlDocPtr doc = xmlReadMemory(xml, (int)strlen(xml), "(no file)", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); + if (!doc) { LOGERROR("CXeromyces: Failed to parse XML string"); return PSRETURN_Xeromyces_XMLParseError; } + { + CScopeLock lock(g_ValidatorCacheLock); + RelaxNGValidator& validator = GetValidator(validatorName); + if (validator.CanValidate() && !validator.ValidateEncoded(doc)) + // For now, log the error and continue, in the future we might fail + LOGERROR("CXeromyces: failed to validate XML string"); + } + WriteBuffer writeBuffer; CreateXMB(doc, writeBuffer); diff --git a/source/ps/XML/Xeromyces.h b/source/ps/XML/Xeromyces.h index a1bfef3057..ae573cb2cc 100644 --- a/source/ps/XML/Xeromyces.h +++ b/source/ps/XML/Xeromyces.h @@ -33,8 +33,8 @@ ERROR_TYPE(Xeromyces, XMLParseError); #include "lib/file/vfs/vfs.h" +class RelaxNGValidator; class WriteBuffer; -class MD5; typedef struct _xmlDoc xmlDoc; typedef xmlDoc* xmlDocPtr; @@ -46,19 +46,19 @@ public: /** * Load from an XML file (with invisible XMB caching). */ - PSRETURN Load(const PIVFS& vfs, const VfsPath& filename); + PSRETURN Load(const PIVFS& vfs, const VfsPath& filename, const std::string& validatorName = ""); /** * Load from an in-memory XML string (with no caching). */ - PSRETURN LoadString(const char* xml); + PSRETURN LoadString(const char* xml, const std::string& validatorName = ""); /** * Convert the given XML file into an XMB in the archive cache. * Returns the XMB path in @p archiveCachePath. * Returns false on error. */ - bool GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath); + bool GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath, const std::string& validatorName = ""); /** * Call once when initialising the program, to load libxml2. @@ -71,8 +71,14 @@ public: */ static void Terminate(); + static bool AddValidator(const PIVFS& vfs, const std::string& name, const VfsPath& grammarPath); + + static bool ValidateEncoded(const std::string& name, const std::wstring& filename, const std::string& document); + private: - PSRETURN ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath); + static RelaxNGValidator& GetValidator(const std::string& name); + + PSRETURN ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath, const std::string& validatorName); bool ReadXMBFile(const PIVFS& vfs, const VfsPath& filename); diff --git a/source/ps/XML/tests/test_RelaxNG.h b/source/ps/XML/tests/test_RelaxNG.h index 3b1098672c..af033755af 100644 --- a/source/ps/XML/tests/test_RelaxNG.h +++ b/source/ps/XML/tests/test_RelaxNG.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -44,7 +44,7 @@ public: { TestLogger logger; TS_ASSERT(!v.Validate(L"doc", L"")); - TS_ASSERT_STR_CONTAINS(logger.GetOutput(), "Parse error: doc:1: Expecting element test, got bogus"); + TS_ASSERT_STR_CONTAINS(logger.GetOutput(), "Validation error: doc:1: Expecting element test, got bogus"); } { diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index 9c2ab4df4d..35083b78df 100644 --- a/source/simulation2/components/CCmpPathfinder.cpp +++ b/source/simulation2/components/CCmpPathfinder.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Profile.h" +#include "ps/XML/Xeromyces.h" #include "renderer/Scene.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpObstruction.h" @@ -56,11 +57,14 @@ void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode)) m_SameTurnMovesCount = 0; + // Register Relax NG validator + CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); + // Since this is used as a system component (not loaded from an entity template), // we can't use the real paramNode (it won't get handled properly when deserializing), // so load the data from a special XML file. CParamNode externalParamNode; - CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml"); + CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); // Previously all move commands during a turn were // queued up and processed asynchronously at the start diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp index 6596062057..f204afc5be 100644 --- a/source/simulation2/components/CCmpTerritoryManager.cpp +++ b/source/simulation2/components/CCmpTerritoryManager.cpp @@ -25,6 +25,7 @@ #include "graphics/TextureManager.h" #include "graphics/TerritoryBoundary.h" #include "maths/MathUtil.h" +#include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "renderer/Scene.h" #include "renderer/TerrainOverlay.h" @@ -120,8 +121,11 @@ public: m_AnimTime = 0.0; + // Register Relax NG validator + CXeromyces::AddValidator(g_VFS, "territorymanager", "simulation/data/territorymanager.rng"); + CParamNode externalParamNode; - CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml"); + CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml", "territorymanager"); int impassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt(); ENSURE(0 <= impassableCost && impassableCost <= 255); diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp index 864756b673..53a661a2a4 100644 --- a/source/simulation2/system/ParamNode.cpp +++ b/source/simulation2/system/ParamNode.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -42,10 +42,10 @@ void CParamNode::LoadXML(CParamNode& ret, const XMBFile& xmb, const wchar_t* sou ret.ApplyLayer(xmb, xmb.GetRoot(), sourceIdentifier); } -void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path) +void CParamNode::LoadXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName) { CXeromyces xero; - PSRETURN ok = xero.Load(g_VFS, path); + PSRETURN ok = xero.Load(g_VFS, path, validatorName); if (ok != PSRETURN_OK) return; // (Xeromyces already logged an error) diff --git a/source/simulation2/system/ParamNode.h b/source/simulation2/system/ParamNode.h index e94c38bffc..4fc911fdf1 100644 --- a/source/simulation2/system/ParamNode.h +++ b/source/simulation2/system/ParamNode.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -134,7 +134,7 @@ public: * Any existing data in @a ret will be overwritten or else kept, so this * can be called multiple times to build up a node from multiple inputs. */ - static void LoadXML(CParamNode& ret, const VfsPath& path); + static void LoadXML(CParamNode& ret, const VfsPath& path, const std::string& validatorName); /** * See LoadXML, but parses the XML string @a xml. diff --git a/source/soundmanager/SoundManager.cpp b/source/soundmanager/SoundManager.cpp index c6484a6a5d..6614bc68f6 100644 --- a/source/soundmanager/SoundManager.cpp +++ b/source/soundmanager/SoundManager.cpp @@ -30,6 +30,7 @@ #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Profiler2.h" +#include "ps/XML/Xeromyces.h" ISoundManager* g_SoundManager = NULL; @@ -253,6 +254,9 @@ CSoundManager::CSoundManager() m_PlayListItems = new PlayList; } + if (!CXeromyces::AddValidator(g_VFS, "sound_group", "audio/sound_group.rng")) + LOGERROR("CSoundManager: failed to load grammar file 'audio/sound_group.rng'"); + RegisterFileReloadFunc(ReloadChangedFileCB, this); } diff --git a/source/soundmanager/scripting/SoundGroup.cpp b/source/soundmanager/scripting/SoundGroup.cpp index ef611bdb18..c96f891f31 100644 --- a/source/soundmanager/scripting/SoundGroup.cpp +++ b/source/soundmanager/scripting/SoundGroup.cpp @@ -283,7 +283,7 @@ void CSoundGroup::Update(float UNUSED(TimeSinceLastFrame)) bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML) { CXeromyces XeroFile; - if (XeroFile.Load(g_VFS, pathnameXML) != PSRETURN_OK) + if (XeroFile.Load(g_VFS, pathnameXML, "sound_group") != PSRETURN_OK) { HandleError(L"error loading file", pathnameXML, ERR::FAIL); return false;