tamlXmlReader.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "persistence/taml/xml/tamlXmlReader.h"
  23. // Debug Profiling.
  24. #include "platform/profiler.h"
  25. #include <tinyxml2.h>
  26. #include "persistence/taml/fsTinyXml.h"
  27. //-----------------------------------------------------------------------------
  28. SimObject* TamlXmlReader::read( FileStream& stream )
  29. {
  30. // Debug Profiling.
  31. PROFILE_SCOPE(TamlXmlReader_Read);
  32. // Create document.
  33. VfsXMLDocument xmlDocument;
  34. // Load document from stream.
  35. if ( !xmlDocument.LoadFile(stream) )
  36. {
  37. // Warn!
  38. Con::warnf("Taml: Could not load Taml XML file from stream.");
  39. return NULL;
  40. }
  41. // Parse root element.
  42. SimObject* pSimObject = parseElement( xmlDocument.RootElement() );
  43. // Reset parse.
  44. resetParse();
  45. return pSimObject;
  46. }
  47. //-----------------------------------------------------------------------------
  48. void TamlXmlReader::resetParse( void )
  49. {
  50. // Debug Profiling.
  51. PROFILE_SCOPE(TamlXmlReader_ResetParse);
  52. // Clear object reference map.
  53. mObjectReferenceMap.clear();
  54. }
  55. //-----------------------------------------------------------------------------
  56. SimObject* TamlXmlReader::parseElement( tinyxml2::XMLElement* pXmlElement )
  57. {
  58. // Debug Profiling.
  59. PROFILE_SCOPE(TamlXmlReader_ParseElement);
  60. SimObject* pSimObject = NULL;
  61. // Fetch element name.
  62. StringTableEntry typeName = StringTable->insert( pXmlElement->Value() );
  63. // Fetch reference to Id.
  64. const U32 tamlRefToId = getTamlRefToId( pXmlElement );
  65. // Do we have a reference to Id?
  66. if ( tamlRefToId != 0 )
  67. {
  68. // Yes, so fetch reference.
  69. typeObjectReferenceHash::Iterator referenceItr = mObjectReferenceMap.find( tamlRefToId );
  70. // Did we find the reference?
  71. if ( referenceItr == mObjectReferenceMap.end() )
  72. {
  73. // No, so warn.
  74. Con::warnf( "Taml: Could not find a reference Id of '%d'", tamlRefToId );
  75. return NULL;
  76. }
  77. // Return object.
  78. return referenceItr->value;
  79. }
  80. // No, so fetch reference Id.
  81. const U32 tamlRefId = getTamlRefId( pXmlElement );
  82. #ifdef TORQUE_DEBUG
  83. // Format the type location.
  84. char typeLocationBuffer[64];
  85. dSprintf( typeLocationBuffer, sizeof(typeLocationBuffer), "Taml [format='xml' line=%d]", pXmlElement->GetLineNum() );
  86. // Create type.
  87. pSimObject = Taml::createType( typeName, mpTaml, typeLocationBuffer );
  88. #else
  89. // Create type.
  90. pSimObject = Taml::createType( typeName, mpTaml );
  91. #endif
  92. // Finish if we couldn't create the type.
  93. if ( pSimObject == NULL )
  94. return NULL;
  95. // Find Taml callbacks.
  96. TamlCallbacks* pCallbacks = dynamic_cast<TamlCallbacks*>( pSimObject );
  97. // Are there any Taml callbacks?
  98. if ( pCallbacks != NULL )
  99. {
  100. // Yes, so call it.
  101. mpTaml->tamlPreRead( pCallbacks );
  102. }
  103. // Parse attributes.
  104. parseAttributes( pXmlElement, pSimObject );
  105. // Fetch object name.
  106. StringTableEntry objectName = StringTable->insert( getTamlObjectName( pXmlElement ) );
  107. // Does the object require a name?
  108. if ( objectName == StringTable->EmptyString() )
  109. {
  110. // No, so just register anonymously.
  111. pSimObject->registerObject();
  112. }
  113. else
  114. {
  115. // Yes, so register a named object.
  116. pSimObject->registerObject( objectName );
  117. // Was the name assigned?
  118. if ( pSimObject->getName() != objectName )
  119. {
  120. // No, so warn that the name was rejected.
  121. #ifdef TORQUE_DEBUG
  122. Con::warnf( "Taml::parseElement() - Registered an instance of type '%s' but a request to name it '%s' was rejected. This is typically because an object of that name already exists. '%s'", typeName, objectName, typeLocationBuffer );
  123. #else
  124. Con::warnf( "Taml::parseElement() - Registered an instance of type '%s' but a request to name it '%s' was rejected. This is typically because an object of that name already exists.", typeName, objectName );
  125. #endif
  126. }
  127. }
  128. // Do we have a reference Id?
  129. if ( tamlRefId != 0 )
  130. {
  131. // Yes, so insert reference.
  132. mObjectReferenceMap.insertUnique( tamlRefId, pSimObject );
  133. }
  134. // Fetch any children.
  135. tinyxml2::XMLNode* pChildXmlNode = pXmlElement->FirstChild();
  136. TamlCustomNodes customProperties;
  137. // Do we have any element children?
  138. if ( pChildXmlNode != NULL )
  139. {
  140. // Fetch the Taml children.
  141. TamlChildren* pChildren = dynamic_cast<TamlChildren*>( pSimObject );
  142. // Fetch any container child class specifier.
  143. AbstractClassRep* pContainerChildClass = pSimObject->getClassRep()->getContainerChildClass( true );
  144. // Iterate siblings.
  145. do
  146. {
  147. // Fetch element.
  148. tinyxml2::XMLElement* pChildXmlElement = pChildXmlNode->ToElement();
  149. // Move to next sibling.
  150. pChildXmlNode = pChildXmlNode->NextSibling();
  151. // Skip if this is not an element?
  152. if ( pChildXmlElement == NULL )
  153. continue;
  154. // Is this a standard child element?
  155. if ( dStrchr( pChildXmlElement->Value(), '.' ) == NULL )
  156. {
  157. // Is this a Taml child?
  158. if ( pChildren == NULL )
  159. {
  160. // No, so warn.
  161. Con::warnf("Taml: Child element '%s' found under parent '%s' but object cannot have children.",
  162. pChildXmlElement->Value(),
  163. pXmlElement->Value() );
  164. // Skip.
  165. continue;
  166. }
  167. // Yes, so parse child element.
  168. SimObject* pChildSimObject = parseElement( pChildXmlElement );
  169. // Skip if the child was not created.
  170. if ( pChildSimObject == NULL )
  171. continue;
  172. // Do we have a container child class?
  173. if ( pContainerChildClass != NULL )
  174. {
  175. // Yes, so is the child object the correctly derived type?
  176. if ( !pChildSimObject->getClassRep()->isClass( pContainerChildClass ) )
  177. {
  178. // No, so warn.
  179. Con::warnf("Taml: Child element '%s' found under parent '%s' but object is restricted to children of type '%s'.",
  180. pChildSimObject->getClassName(),
  181. pSimObject->getClassName(),
  182. pContainerChildClass->getClassName() );
  183. // NOTE: We can't delete the object as it may be referenced elsewhere!
  184. pChildSimObject = NULL;
  185. // Skip.
  186. continue;
  187. }
  188. }
  189. // Add child.
  190. pChildren->addTamlChild( pChildSimObject );
  191. // Find Taml callbacks for child.
  192. TamlCallbacks* pChildCallbacks = dynamic_cast<TamlCallbacks*>( pChildSimObject );
  193. // Do we have callbacks on the child?
  194. if ( pChildCallbacks != NULL )
  195. {
  196. // Yes, so perform callback.
  197. mpTaml->tamlAddParent( pChildCallbacks, pSimObject );
  198. }
  199. }
  200. else
  201. {
  202. // No, so parse custom element.
  203. parseCustomElement( pChildXmlElement, customProperties );
  204. }
  205. }
  206. while( pChildXmlNode != NULL );
  207. // Call custom read.
  208. mpTaml->tamlCustomRead( pCallbacks, customProperties );
  209. }
  210. // Are there any Taml callbacks?
  211. if ( pCallbacks != NULL )
  212. {
  213. // Yes, so call it.
  214. mpTaml->tamlPostRead( pCallbacks, customProperties );
  215. }
  216. // Return object.
  217. return pSimObject;
  218. }
  219. //-----------------------------------------------------------------------------
  220. void TamlXmlReader::parseAttributes( tinyxml2::XMLElement* pXmlElement, SimObject* pSimObject )
  221. {
  222. // Debug Profiling.
  223. PROFILE_SCOPE(TamlXmlReader_ParseAttributes);
  224. // Sanity!
  225. AssertFatal( pSimObject != NULL, "Taml: Cannot parse attributes on a NULL object." );
  226. // Iterate attributes.
  227. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  228. {
  229. // Insert attribute name.
  230. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  231. // Ignore if this is a Taml attribute.
  232. if ( attributeName == tamlRefIdName ||
  233. attributeName == tamlRefToIdName ||
  234. attributeName == tamlNamedObjectName )
  235. continue;
  236. // Set the field.
  237. pSimObject->setPrefixedDataField(attributeName, NULL, pAttribute->Value());
  238. }
  239. }
  240. //-----------------------------------------------------------------------------
  241. void TamlXmlReader::parseCustomElement( tinyxml2::XMLElement* pXmlElement, TamlCustomNodes& customNodes )
  242. {
  243. // Debug Profiling.
  244. PROFILE_SCOPE(TamlXmlReader_ParseCustomElement);
  245. // Is this a standard child element?
  246. const char* pPeriod = dStrchr( pXmlElement->Value(), '.' );
  247. // Sanity!
  248. AssertFatal( pPeriod != NULL, "Parsing extended element but no period character found." );
  249. // Fetch any custom XML node.
  250. tinyxml2::XMLNode* pCustomXmlNode = pXmlElement->FirstChild();
  251. // Finish is no XML node exists.
  252. if ( pCustomXmlNode == NULL )
  253. return;
  254. // Yes, so add custom node.
  255. TamlCustomNode* pCustomNode = customNodes.addNode( pPeriod+1 );
  256. do
  257. {
  258. // Fetch element.
  259. tinyxml2::XMLElement* pCustomXmlElement = pCustomXmlNode->ToElement();
  260. // Move to next sibling.
  261. pCustomXmlNode = pCustomXmlNode->NextSibling();
  262. // Skip if this is not an element.
  263. if ( pCustomXmlElement == NULL )
  264. continue;
  265. // Parse custom node.
  266. parseCustomNode( pCustomXmlElement, pCustomNode );
  267. }
  268. while ( pCustomXmlNode != NULL );
  269. }
  270. //-----------------------------------------------------------------------------
  271. void TamlXmlReader::parseCustomNode( tinyxml2::XMLElement* pXmlElement, TamlCustomNode* pCustomNode )
  272. {
  273. // Is the node a proxy object?
  274. if ( getTamlRefId( pXmlElement ) != 0 || getTamlRefToId( pXmlElement ) != 0 )
  275. {
  276. // Yes, so parse proxy object.
  277. SimObject* pProxyObject = parseElement( pXmlElement );
  278. // Add child node.
  279. pCustomNode->addNode( pProxyObject );
  280. return;
  281. }
  282. // Yes, so add child node.
  283. TamlCustomNode* pChildNode = pCustomNode->addNode( pXmlElement->Value() );
  284. // Iterate attributes.
  285. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  286. {
  287. // Insert attribute name.
  288. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  289. // Skip if a Taml reference attribute.
  290. if ( attributeName == tamlRefIdName || attributeName == tamlRefToIdName )
  291. continue;
  292. // Add node field.
  293. pChildNode->addField( attributeName, pAttribute->Value() );
  294. }
  295. // Fetch any element text.
  296. const char* pElementText = pXmlElement->GetText();
  297. // Do we have any element text?
  298. if ( pElementText != NULL )
  299. {
  300. // Yes, so store it.
  301. pChildNode->setNodeText( pElementText );
  302. }
  303. // Fetch any children.
  304. tinyxml2::XMLNode* pChildXmlNode = pXmlElement->FirstChild();
  305. // Do we have any element children?
  306. if ( pChildXmlNode != NULL )
  307. {
  308. do
  309. {
  310. // Yes, so fetch child element.
  311. tinyxml2::XMLElement* pChildXmlElement = pChildXmlNode->ToElement();
  312. // Move to next sibling.
  313. pChildXmlNode = pChildXmlNode->NextSibling();
  314. // Skip if this is not an element.
  315. if ( pChildXmlElement == NULL )
  316. continue;
  317. // Parse custom node.
  318. parseCustomNode( pChildXmlElement, pChildNode );
  319. }
  320. while( pChildXmlNode != NULL );
  321. }
  322. }
  323. //-----------------------------------------------------------------------------
  324. U32 TamlXmlReader::getTamlRefId( tinyxml2::XMLElement* pXmlElement )
  325. {
  326. // Debug Profiling.
  327. PROFILE_SCOPE(TamlXmlReader_GetTamlRefId);
  328. // Iterate attributes.
  329. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  330. {
  331. // Insert attribute name.
  332. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  333. // Skip if not the correct attribute.
  334. if ( attributeName != tamlRefIdName )
  335. continue;
  336. // Return it.
  337. return dAtoi( pAttribute->Value() );
  338. }
  339. // Not found.
  340. return 0;
  341. }
  342. //-----------------------------------------------------------------------------
  343. U32 TamlXmlReader::getTamlRefToId( tinyxml2::XMLElement* pXmlElement )
  344. {
  345. // Debug Profiling.
  346. PROFILE_SCOPE(TamlXmlReader_GetTamlRefToId);
  347. // Iterate attributes.
  348. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  349. {
  350. // Insert attribute name.
  351. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  352. // Skip if not the correct attribute.
  353. if ( attributeName != tamlRefToIdName )
  354. continue;
  355. // Return it.
  356. return dAtoi( pAttribute->Value() );
  357. }
  358. // Not found.
  359. return 0;
  360. }
  361. //-----------------------------------------------------------------------------
  362. const char* TamlXmlReader::getTamlObjectName( tinyxml2::XMLElement* pXmlElement )
  363. {
  364. // Debug Profiling.
  365. PROFILE_SCOPE(TamlXmlReader_GetTamlObjectName);
  366. // Iterate attributes.
  367. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  368. {
  369. // Insert attribute name.
  370. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  371. // Skip if not the correct attribute.
  372. if ( attributeName != tamlNamedObjectName )
  373. continue;
  374. // Return it.
  375. return pAttribute->Value();
  376. }
  377. // Not found.
  378. return NULL;
  379. }