# Initial support for automatic validation of entity template XML.
Add RelaxNG schemas for all current components. Add -dumpSchema command-line option to dump the combined entity schema. Add a Perl script to validate entity templates against the schema. See #413. This was SVN commit r7452.
This commit is contained in:
parent
336817a849
commit
40688ec5df
@ -1,5 +1,16 @@
|
||||
function Armour() {}
|
||||
|
||||
Armour.prototype.Schema =
|
||||
"<element name='Hack'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='Pierce'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='Crush'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>";
|
||||
|
||||
Armour.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
@ -7,10 +18,9 @@ Armour.prototype.Init = function()
|
||||
Armour.prototype.TakeDamage = function(hack, pierce, crush)
|
||||
{
|
||||
// Adjust damage values based on armour
|
||||
// (Default armour values to 0 if undefined)
|
||||
var adjHack = Math.max(0, hack - (this.template.Hack || 0));
|
||||
var adjPierce = Math.max(0, pierce - (this.template.Pierce || 0));
|
||||
var adjCrush = Math.max(0, crush - (this.template.Crush || 0));
|
||||
var adjHack = Math.max(0, hack - this.template.Hack);
|
||||
var adjPierce = Math.max(0, pierce - this.template.Pierce);
|
||||
var adjCrush = Math.max(0, crush - this.template.Crush);
|
||||
|
||||
// Total is sum of individual damages, with minimum damage 1
|
||||
var total = Math.max(1, adjHack + adjPierce + adjCrush);
|
||||
@ -22,11 +32,11 @@ Armour.prototype.TakeDamage = function(hack, pierce, crush)
|
||||
|
||||
Armour.prototype.GetArmourStrengths = function()
|
||||
{
|
||||
// Convert attack values to numbers, default 0 if unspecified
|
||||
// Convert attack values to numbers
|
||||
return {
|
||||
hack: +(this.template.Hack || 0),
|
||||
pierce: +(this.template.Pierce || 0),
|
||||
crush: +(this.template.Crush || 0)
|
||||
hack: +this.template.Hack,
|
||||
pierce: +this.template.Pierce,
|
||||
crush: +this.template.Crush
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,39 @@
|
||||
function Attack() {}
|
||||
|
||||
Attack.prototype.Schema =
|
||||
"<element name='Hack'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='Pierce'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='Crush'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='Range'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='MinRange'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='PrepareTime'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='RepeatTime'>" +
|
||||
"<data type='positiveInteger'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='ProjectileSpeed'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>";
|
||||
|
||||
Attack.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
@ -1,5 +1,14 @@
|
||||
function Builder() {}
|
||||
|
||||
Builder.prototype.Schema =
|
||||
"<element name='Entities'>" +
|
||||
"<attribute name='datatype'><value>tokens</value></attribute>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"<element name='Rate'>" +
|
||||
"<ref name='positiveDecimal'/>" +
|
||||
"</element>";
|
||||
|
||||
Builder.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
@ -1,5 +1,30 @@
|
||||
function Cost() {}
|
||||
|
||||
Cost.prototype.Schema =
|
||||
"<optional>" +
|
||||
"<element name='Population'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='PopulationBonus'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='BuildTime'>" +
|
||||
"<ref name='positiveDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<element name='Resources'>" +
|
||||
"<interleave>" +
|
||||
"<element name='food'><data type='nonNegativeInteger'/></element>" +
|
||||
"<element name='wood'><data type='nonNegativeInteger'/></element>" +
|
||||
"<element name='stone'><data type='nonNegativeInteger'/></element>" +
|
||||
"<element name='metal'><data type='nonNegativeInteger'/></element>" +
|
||||
"</interleave>" +
|
||||
"</element>";
|
||||
|
||||
Cost.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
@ -26,10 +51,10 @@ Cost.prototype.GetBuildTime = function()
|
||||
Cost.prototype.GetResourceCosts = function()
|
||||
{
|
||||
return {
|
||||
"food": +(this.template.Resources.food || 0),
|
||||
"wood": +(this.template.Resources.wood || 0),
|
||||
"stone": +(this.template.Resources.stone || 0),
|
||||
"metal": +(this.template.Resources.metal || 0)
|
||||
"food": +this.template.Resources.food,
|
||||
"wood": +this.template.Resources.wood,
|
||||
"stone": +this.template.Resources.stone,
|
||||
"metal": +this.template.Resources.metal
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,25 @@
|
||||
function Health() {}
|
||||
|
||||
Health.prototype.Schema =
|
||||
"<element name='Max'>" +
|
||||
"<data type='positiveInteger'/>" +
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='Initial'>" +
|
||||
"<data type='positiveInteger'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='RegenRate'>" +
|
||||
"<ref name='positiveDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='DeathType'>" +
|
||||
"<value>corpse</value>" +
|
||||
"</element>" +
|
||||
"</optional>";
|
||||
|
||||
Health.prototype.Init = function()
|
||||
{
|
||||
// Default to <Initial>, but use <Max> if it's undefined or zero
|
||||
|
@ -1,5 +1,24 @@
|
||||
function Identity() {}
|
||||
|
||||
Identity.prototype.Schema =
|
||||
"<element name='Civ'>" +
|
||||
"<text/>" + // TODO: <choice>?
|
||||
"</element>" +
|
||||
"<element name='GenericName'>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='SpecificName'>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<element name='IconCell'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"<element name='IconSheet'>" +
|
||||
"<text/>" +
|
||||
"</element>";
|
||||
|
||||
Identity.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
@ -1,5 +1,23 @@
|
||||
function ResourceGatherer() {}
|
||||
|
||||
ResourceGatherer.prototype.Schema =
|
||||
"<element name='Rates'>" +
|
||||
"<interleave>" +
|
||||
"<optional><element name='food'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='wood'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='stone'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='metal'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='food.fish'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='food.fruit'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='food.grain'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='food.meat'><data type='decimal'/></element></optional>" +
|
||||
"<optional><element name='food.milk'><data type='decimal'/></element></optional>" +
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
"<element name='BaseSpeed'>" +
|
||||
"<data type='positiveInteger'/>" +
|
||||
"</element>";
|
||||
|
||||
ResourceGatherer.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
@ -1,5 +1,35 @@
|
||||
function ResourceSupply() {}
|
||||
|
||||
ResourceSupply.prototype.Schema =
|
||||
"<element name='Amount'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"<choice>" +
|
||||
"<interleave>" +
|
||||
"<element name='Type'><value>food</value></element>" +
|
||||
"<element name='Subtype'><value>fish</value></element>" +
|
||||
"</interleave>" +
|
||||
"<interleave>" +
|
||||
"<element name='Type'><value>food</value></element>" +
|
||||
"<element name='Subtype'><value>fruit</value></element>" +
|
||||
"</interleave>" +
|
||||
"<interleave>" +
|
||||
"<element name='Type'><value>food</value></element>" +
|
||||
"<element name='Subtype'><value>grain</value></element>" +
|
||||
"</interleave>" +
|
||||
"<interleave>" +
|
||||
"<element name='Type'><value>food</value></element>" +
|
||||
"<element name='Subtype'><value>meat</value></element>" +
|
||||
"</interleave>" +
|
||||
"<interleave>" +
|
||||
"<element name='Type'><value>food</value></element>" +
|
||||
"<element name='Subtype'><value>milk</value></element>" +
|
||||
"</interleave>" +
|
||||
"<element name='Type'><value>wood</value></element>" +
|
||||
"<element name='Type'><value>stone</value></element>" +
|
||||
"<element name='Type'><value>metal</value></element>" +
|
||||
"</choice>";
|
||||
|
||||
ResourceSupply.prototype.Init = function()
|
||||
{
|
||||
// Current resource amount (non-negative; can be a fractional amount)
|
||||
|
@ -1,5 +1,15 @@
|
||||
function Sound() {}
|
||||
|
||||
Sound.prototype.Schema =
|
||||
"<element name='SoundGroups'>" +
|
||||
"<zeroOrMore>" +
|
||||
"<element>" +
|
||||
"<anyName/>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"</zeroOrMore>" +
|
||||
"</element>";
|
||||
|
||||
Sound.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -856,6 +856,19 @@ void Init(const CmdLineArgs& args, int flags)
|
||||
// (required for finding our output log files).
|
||||
g_Logger = new CLogger;
|
||||
|
||||
// Special command-line mode to dump the entity schemas instead of running the game.
|
||||
// (This must be done after loading VFS etc, but should be done before wasting time
|
||||
// on anything else.)
|
||||
if (args.Has("dumpSchema"))
|
||||
{
|
||||
CSimulation2 sim(NULL, NULL);
|
||||
sim.LoadDefaultScripts();
|
||||
std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc);
|
||||
f << sim.GenerateSchema();
|
||||
std::cout << "Generated entity.rng\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Call LoadLanguage(NULL) to initialize the I18n system, but
|
||||
// without loading an actual language file - translate() will
|
||||
// just show the English key text, which is better than crashing
|
||||
|
@ -306,3 +306,8 @@ bool CSimulation2::DeserializeState(std::istream& stream)
|
||||
// TODO: need to make sure the required SYSTEM_ENTITY components get constructed
|
||||
return m->m_ComponentManager.DeserializeState(stream);
|
||||
}
|
||||
|
||||
std::string CSimulation2::GenerateSchema()
|
||||
{
|
||||
return m->m_ComponentManager.GenerateSchema();
|
||||
}
|
||||
|
@ -128,6 +128,8 @@ public:
|
||||
bool SerializeState(std::ostream& stream);
|
||||
bool DeserializeState(std::istream& stream);
|
||||
|
||||
std::string GenerateSchema();
|
||||
|
||||
private:
|
||||
CSimulation2Impl* m;
|
||||
|
||||
|
@ -36,6 +36,23 @@ public:
|
||||
CFixed_23_8 m_Size1; // height/radius
|
||||
CFixed_23_8 m_Height;
|
||||
|
||||
static std::string GetSchema()
|
||||
{
|
||||
return
|
||||
"<choice>"
|
||||
"<element name='Square'>"
|
||||
"<attribute name='width'><ref name='positiveDecimal'/></attribute>"
|
||||
"<attribute name='depth'><ref name='positiveDecimal'/></attribute>"
|
||||
"</element>"
|
||||
"<element name='Circle'>"
|
||||
"<attribute name='radius'><ref name='positiveDecimal'/></attribute>"
|
||||
"</element>"
|
||||
"</choice>"
|
||||
"<element name='Height'>"
|
||||
"<ref name='nonNegativeDecimal'/>"
|
||||
"</element>";
|
||||
}
|
||||
|
||||
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& paramNode)
|
||||
{
|
||||
if (paramNode.GetChild("Square").IsOk())
|
||||
|
@ -46,6 +46,13 @@ public:
|
||||
|
||||
ICmpObstructionManager::tag_t m_Tag;
|
||||
|
||||
static std::string GetSchema()
|
||||
{
|
||||
return
|
||||
"<optional>"
|
||||
"<element name='Inactive'><empty/></element>"
|
||||
"</optional>";
|
||||
}
|
||||
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
|
||||
{
|
||||
m_Context = &context;
|
||||
|
@ -75,27 +75,24 @@ public:
|
||||
|
||||
bool m_Dirty; // true if position/rotation has changed since last TurnStart
|
||||
|
||||
/*
|
||||
* Schema: (untested)
|
||||
*
|
||||
* <element name="Position">
|
||||
* <interleave>
|
||||
* <element name="Anchor" a:help="Automatic rotation to follow the slope of terrain">
|
||||
* <choice>
|
||||
* <value a:help="Always stand straight up">upright</value>
|
||||
* <value a:help="Rotate backwards and forwards to follow the terrain">pitch</value>
|
||||
* <value a:help="Rotate in all direction to follow the terrain">pitch-roll</value>
|
||||
* </choice>
|
||||
* </element>
|
||||
* <element name="Altitude" a:help="Height above terrain in metres">
|
||||
* <data type="float"/>
|
||||
* </element>
|
||||
* <element name="Floating" a:help="Whether the entity floats on water">
|
||||
* <data type="boolean"/>
|
||||
* </element>
|
||||
* </interleave>
|
||||
* </element>
|
||||
*/
|
||||
static std::string GetSchema()
|
||||
{
|
||||
return
|
||||
"<element name='Anchor' a:help='Automatic rotation to follow the slope of terrain'>"
|
||||
"<choice>"
|
||||
"<value a:help='Always stand straight up'>upright</value>"
|
||||
"<value a:help='Rotate backwards and forwards to follow the terrain'>pitch</value>"
|
||||
"<value a:help='Rotate in all direction to follow the terrain'>pitch-roll</value>"
|
||||
"</choice>"
|
||||
"</element>"
|
||||
"<element name='Altitude' a:help='Height above terrain in metres'>"
|
||||
"<data type='decimal'/>"
|
||||
"</element>"
|
||||
"<element name='Floating' a:help='Whether the entity floats on water'>"
|
||||
"<data type='boolean'/>"
|
||||
"</element>";
|
||||
}
|
||||
|
||||
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
|
||||
{
|
||||
m_Context = &context;
|
||||
|
@ -56,6 +56,13 @@ public:
|
||||
};
|
||||
int m_State;
|
||||
|
||||
static std::string GetSchema()
|
||||
{
|
||||
return
|
||||
"<element name='WalkSpeed'>"
|
||||
"<ref name='positiveDecimal'/>"
|
||||
"</element>";
|
||||
}
|
||||
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
|
||||
{
|
||||
m_Context = &context;
|
||||
|
@ -60,6 +60,17 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static std::string GetSchema()
|
||||
{
|
||||
return
|
||||
"<optional>"
|
||||
"<element name='Foundation'><empty/></element>"
|
||||
"</optional>"
|
||||
"<optional>"
|
||||
"<element name='FoundationActor'><text/></element>"
|
||||
"</optional>"
|
||||
"<element name='Actor'><text/></element>";
|
||||
}
|
||||
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
|
||||
{
|
||||
if (!context.HasUnitManager())
|
||||
@ -68,7 +79,7 @@ public:
|
||||
// TODO: we should do some fancy animation of under-construction buildings rising from the ground,
|
||||
// but for now we'll just use the foundation actor and ignore the normal one
|
||||
std::string name;
|
||||
if (paramNode.GetChild("Foundation").IsOk())
|
||||
if (paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk())
|
||||
name = utf8_from_wstring(paramNode.GetChild("FoundationActor").ToString());
|
||||
else
|
||||
name = utf8_from_wstring(paramNode.GetChild("Actor").ToString());
|
||||
|
@ -30,14 +30,14 @@
|
||||
#define REGISTER_COMPONENT_TYPE(cname) \
|
||||
void RegisterComponentType_##cname(CComponentManager& mgr) \
|
||||
{ \
|
||||
mgr.RegisterComponentType(CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, #cname); \
|
||||
mgr.RegisterComponentType(CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, #cname, CCmp##cname::GetSchema()); \
|
||||
CCmp##cname::ClassInit(mgr); \
|
||||
}
|
||||
|
||||
#define REGISTER_COMPONENT_SCRIPT_WRAPPER(cname) \
|
||||
void RegisterComponentType_##cname(CComponentManager& mgr) \
|
||||
{ \
|
||||
mgr.RegisterComponentTypeScriptWrapper(CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, #cname); \
|
||||
mgr.RegisterComponentTypeScriptWrapper(CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, #cname, CCmp##cname::GetSchema()); \
|
||||
CCmp##cname::ClassInit(mgr); \
|
||||
}
|
||||
|
||||
|
@ -185,8 +185,18 @@ void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std:
|
||||
mustReloadComponents = true;
|
||||
}
|
||||
|
||||
std::string schema = "<empty/>";
|
||||
{
|
||||
CScriptValRooted prototype;
|
||||
if (componentManager->m_ScriptInterface.GetProperty(ctor.get(), "prototype", prototype) &&
|
||||
componentManager->m_ScriptInterface.HasProperty(prototype.get(), "Schema"))
|
||||
{
|
||||
componentManager->m_ScriptInterface.GetProperty(prototype.get(), "Schema", schema);
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a new ComponentType, using the wrapper's alloc functions
|
||||
ComponentType ct = { CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, ctor.get() };
|
||||
ComponentType ct = { CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, schema, ctor.get() };
|
||||
componentManager->m_ComponentTypesById[cid] = ct;
|
||||
|
||||
componentManager->m_CurrentComponent = cid; // needed by Subscribe
|
||||
@ -365,17 +375,17 @@ void CComponentManager::ResetState()
|
||||
}
|
||||
|
||||
void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc,
|
||||
const char* name)
|
||||
const char* name, const std::string& schema)
|
||||
{
|
||||
ComponentType c = { CT_Native, iid, alloc, dealloc, name, 0 };
|
||||
ComponentType c = { CT_Native, iid, alloc, dealloc, name, schema, 0 };
|
||||
m_ComponentTypesById.insert(std::make_pair(cid, c));
|
||||
m_ComponentTypeIdsByName[name] = cid;
|
||||
}
|
||||
|
||||
void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc,
|
||||
DeallocFunc dealloc, const char* name)
|
||||
DeallocFunc dealloc, const char* name, const std::string& schema)
|
||||
{
|
||||
ComponentType c = { CT_ScriptWrapper, iid, alloc, dealloc, name, 0 };
|
||||
ComponentType c = { CT_ScriptWrapper, iid, alloc, dealloc, name, schema, 0 };
|
||||
m_ComponentTypesById.insert(std::make_pair(cid, c));
|
||||
m_ComponentTypeIdsByName[name] = cid;
|
||||
// TODO: merge with RegisterComponentType
|
||||
@ -715,3 +725,68 @@ void CComponentManager::SendGlobalMessage(const CMessage& msg) const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string CComponentManager::GenerateSchema()
|
||||
{
|
||||
std::string schema =
|
||||
"<grammar xmlns='http://relaxng.org/ns/structure/1.0' xmlns:a='http://ns.wildfiregames.com/entity' datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'>"
|
||||
"<define name='nonNegativeDecimal'>"
|
||||
"<data type='decimal'><param name='minInclusive'>0</param></data>"
|
||||
"</define>"
|
||||
"<define name='positiveDecimal'>"
|
||||
"<data type='decimal'><param name='minExclusive'>0</param></data>"
|
||||
"</define>"
|
||||
"<define name='anything'>"
|
||||
"<zeroOrMore>"
|
||||
"<choice>"
|
||||
"<attribute><anyName/></attribute>"
|
||||
"<text/>"
|
||||
"<element>"
|
||||
"<anyName/>"
|
||||
"<ref name='anything'/>"
|
||||
"</element>"
|
||||
"</choice>"
|
||||
"</zeroOrMore>"
|
||||
"</define>";
|
||||
|
||||
std::map<InterfaceId, std::vector<std::string> > interfaceComponentTypes;
|
||||
|
||||
for (std::map<ComponentTypeId, ComponentType>::const_iterator it = m_ComponentTypesById.begin(); it != m_ComponentTypesById.end(); ++it)
|
||||
{
|
||||
schema +=
|
||||
"<define name='component." + it->second.name + "'>"
|
||||
"<element name='" + it->second.name + "'>"
|
||||
"<interleave>" + it->second.schema + "</interleave>"
|
||||
"</element>"
|
||||
"</define>";
|
||||
|
||||
interfaceComponentTypes[it->second.iid].push_back(it->second.name);
|
||||
}
|
||||
|
||||
for (std::map<std::string, InterfaceId>::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it)
|
||||
{
|
||||
schema += "<define name='interface." + it->first + "'><choice>";
|
||||
std::vector<std::string>& cts = interfaceComponentTypes[it->second];
|
||||
for (size_t i = 0; i < cts.size(); ++i)
|
||||
schema += "<ref name='component." + cts[i] + "'/>";
|
||||
schema += "</choice></define>";
|
||||
}
|
||||
|
||||
schema +=
|
||||
"<start>"
|
||||
"<element name='Entity'>"
|
||||
"<optional><attribute name='parent'/></optional>"
|
||||
"<interleave>";
|
||||
for (std::map<std::string, InterfaceId>::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it)
|
||||
schema += "<optional><ref name='interface." + it->first + "'/></optional>";
|
||||
schema +=
|
||||
"</interleave>"
|
||||
"</element>"
|
||||
"</start>";
|
||||
|
||||
schema += "</grammar>";
|
||||
|
||||
// TODO: pretty-print
|
||||
return schema;
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ private:
|
||||
AllocFunc alloc;
|
||||
DeallocFunc dealloc;
|
||||
std::string name;
|
||||
std::string schema; // RelaxNG fragment
|
||||
jsval ctor; // only valid if type == CT_Script
|
||||
};
|
||||
|
||||
@ -82,8 +83,8 @@ public:
|
||||
|
||||
void RegisterMessageType(MessageTypeId mtid, const char* name);
|
||||
|
||||
void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*);
|
||||
void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*);
|
||||
void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema);
|
||||
void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema);
|
||||
void SubscribeToMessageType(MessageTypeId);
|
||||
void SubscribeGloballyToMessageType(MessageTypeId);
|
||||
|
||||
@ -191,6 +192,8 @@ public:
|
||||
bool SerializeState(std::ostream& stream);
|
||||
bool DeserializeState(std::istream& stream);
|
||||
|
||||
std::string GenerateSchema();
|
||||
|
||||
ScriptInterface& GetScriptInterface() { return m_ScriptInterface; }
|
||||
|
||||
private:
|
||||
|
@ -23,6 +23,12 @@ IComponent::~IComponent()
|
||||
{
|
||||
}
|
||||
|
||||
std::string IComponent::GetSchema()
|
||||
{
|
||||
// No schema specified -> allow only empty elements
|
||||
return "<empty/>";
|
||||
}
|
||||
|
||||
void IComponent::HandleMessage(const CSimContext& UNUSED(context), const CMessage& UNUSED(msg), bool UNUSED(global))
|
||||
{
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ class IComponent
|
||||
public:
|
||||
virtual ~IComponent();
|
||||
|
||||
static std::string GetSchema();
|
||||
|
||||
virtual void Init(const CSimContext& context, const CParamNode& paramNode) = 0;
|
||||
virtual void Deinit(const CSimContext& context) = 0;
|
||||
|
||||
|
154
source/tools/entvalidate/entvalidate.pl
Normal file
154
source/tools/entvalidate/entvalidate.pl
Normal file
@ -0,0 +1,154 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use XML::Parser;
|
||||
use XML::LibXML;
|
||||
use Data::Dumper;
|
||||
use Storable qw(dclone);
|
||||
use File::Find;
|
||||
|
||||
my $root = '../../../binaries/data/mods/public/simulation/templates';
|
||||
my $rngschema = XML::LibXML::RelaxNG->new(location =>'../../../binaries/system/entity.rng');
|
||||
|
||||
sub get_file
|
||||
{
|
||||
my ($vfspath) = @_;
|
||||
my $fn = "$root/$vfspath.xml";
|
||||
open my $f, $fn or die "Error loading $fn: $!";
|
||||
local $/;
|
||||
return <$f>;
|
||||
}
|
||||
|
||||
sub trim
|
||||
{
|
||||
my ($t) = @_;
|
||||
return '' if not defined $t;
|
||||
$t =~ /^\s*(.*?)\s*$/s;
|
||||
return $1;
|
||||
}
|
||||
|
||||
sub load_xml
|
||||
{
|
||||
my ($file) = @_;
|
||||
my $root = {};
|
||||
my @stack = ($root);
|
||||
my $p = new XML::Parser(Handlers => {
|
||||
Start => sub {
|
||||
my ($e, $n, %a) = @_;
|
||||
my $t = {};
|
||||
die "Duplicate child node '$n'" if exists $stack[-1]{$n};
|
||||
$stack[-1]{$n} = $t;
|
||||
for (keys %a) {
|
||||
$t->{'@'.$_}{' content'} = trim($a{$_});
|
||||
}
|
||||
push @stack, $t;
|
||||
},
|
||||
End => sub {
|
||||
my ($e, $n) = @_;
|
||||
$stack[-1]{' content'} = trim($stack[-1]{' content'});
|
||||
pop @stack;
|
||||
},
|
||||
Char => sub {
|
||||
my ($e, $str) = @_;
|
||||
$stack[-1]{' content'} .= $str;
|
||||
},
|
||||
});
|
||||
$p->parse($file);
|
||||
|
||||
return $root;
|
||||
}
|
||||
|
||||
sub apply_layer
|
||||
{
|
||||
my ($base, $new) = @_;
|
||||
$base->{' content'} = $new->{' content'};
|
||||
for my $k (grep $_ ne ' content', keys %$new) {
|
||||
if ($new->{$k}{'@disable'}) {
|
||||
delete $base->{$k};
|
||||
} else {
|
||||
if ($new->{$k}{'@replace'}) {
|
||||
delete $base->{$k};
|
||||
}
|
||||
$base->{$k} ||= {};
|
||||
apply_layer($base->{$k}, $new->{$k});
|
||||
delete $base->{$k}{'@replace'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub load_inherited
|
||||
{
|
||||
my ($vfspath) = @_;
|
||||
my $layer = load_xml(get_file($vfspath));
|
||||
|
||||
if ($layer->{Entity}{'@parent'}) {
|
||||
my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'});
|
||||
apply_layer($parent->{Entity}, $layer->{Entity});
|
||||
return $parent;
|
||||
} else {
|
||||
return $layer;
|
||||
}
|
||||
}
|
||||
|
||||
sub escape_xml
|
||||
{
|
||||
my ($t) = @_;
|
||||
$t =~ s/&/&/g;
|
||||
$t =~ s/</</g;
|
||||
$t =~ s/>/>/g;
|
||||
$t =~ s/"/"/g;
|
||||
$t =~ s/\t/	/g;
|
||||
$t =~ s/\n/ /g;
|
||||
$t =~ s/\r/ /g;
|
||||
$t;
|
||||
}
|
||||
|
||||
sub to_xml
|
||||
{
|
||||
my ($e) = @_;
|
||||
my $r = $e->{' content'};
|
||||
$r = '' if not defined $r;
|
||||
for my $k (sort grep !/^[\@ ]/, keys %$e) {
|
||||
$r .= "<$k";
|
||||
for my $a (sort grep /^\@/, keys %{$e->{$k}}) {
|
||||
$a =~ /^\@(.*)/;
|
||||
$r .= " $1=\"".escape_xml($e->{$k}{$a}{' content'})."\"";
|
||||
}
|
||||
$r .= ">";
|
||||
$r .= to_xml($e->{$k});
|
||||
$r .= "</$k>";
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
sub validate
|
||||
{
|
||||
my ($vfspath) = @_;
|
||||
my $xml = to_xml(load_inherited($vfspath));
|
||||
my $doc = XML::LibXML->new->parse_string($xml);
|
||||
$rngschema->validate($doc);
|
||||
}
|
||||
|
||||
my @files;
|
||||
sub find_process {
|
||||
return $File::Find::prune = 1 if $_ eq '.svn';
|
||||
my $n = $File::Find::name;
|
||||
return if /~$/;
|
||||
return unless -f $_;
|
||||
$n =~ s/\Q$root\///;
|
||||
$n =~ s/\.xml$//;
|
||||
push @files, $n;
|
||||
}
|
||||
find({ wanted => \&find_process }, $root);
|
||||
|
||||
for my $f (sort @files) {
|
||||
next if $f =~ /^template_/;
|
||||
print "# $f...\n";
|
||||
eval {
|
||||
validate($f);
|
||||
};
|
||||
if ($@) {
|
||||
print $@;
|
||||
eval { print to_xml(load_inherited($f)), "\n"; }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user