tamlXmlReader.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  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. //See if we have any sort of array index
  237. S32 suffixNum;
  238. String trimmedName = String::GetTrailingNumber(attributeName, suffixNum);
  239. if (!trimmedName.equal(attributeName))
  240. {
  241. char arrayIndexStr[32];
  242. dItoa(suffixNum, arrayIndexStr);
  243. // Set the field.
  244. pSimObject->setPrefixedDataField(StringTable->insert(trimmedName.c_str()), arrayIndexStr, pAttribute->Value());
  245. }
  246. else
  247. {
  248. // Set the field.
  249. pSimObject->setPrefixedDataField(attributeName, NULL, pAttribute->Value());
  250. }
  251. }
  252. }
  253. //-----------------------------------------------------------------------------
  254. void TamlXmlReader::parseCustomElement( tinyxml2::XMLElement* pXmlElement, TamlCustomNodes& customNodes )
  255. {
  256. // Debug Profiling.
  257. PROFILE_SCOPE(TamlXmlReader_ParseCustomElement);
  258. // Is this a standard child element?
  259. const char* pPeriod = dStrchr( pXmlElement->Value(), '.' );
  260. // Sanity!
  261. AssertFatal( pPeriod != NULL, "Parsing extended element but no period character found." );
  262. // Fetch any custom XML node.
  263. tinyxml2::XMLNode* pCustomXmlNode = pXmlElement->FirstChild();
  264. // Finish is no XML node exists.
  265. if ( pCustomXmlNode == NULL )
  266. return;
  267. // Yes, so add custom node.
  268. TamlCustomNode* pCustomNode = customNodes.addNode( pPeriod+1 );
  269. do
  270. {
  271. // Fetch element.
  272. tinyxml2::XMLElement* pCustomXmlElement = pCustomXmlNode->ToElement();
  273. // Move to next sibling.
  274. pCustomXmlNode = pCustomXmlNode->NextSibling();
  275. // Skip if this is not an element.
  276. if ( pCustomXmlElement == NULL )
  277. continue;
  278. // Parse custom node.
  279. parseCustomNode( pCustomXmlElement, pCustomNode );
  280. }
  281. while ( pCustomXmlNode != NULL );
  282. }
  283. //-----------------------------------------------------------------------------
  284. void TamlXmlReader::parseCustomNode( tinyxml2::XMLElement* pXmlElement, TamlCustomNode* pCustomNode )
  285. {
  286. // Is the node a proxy object?
  287. if ( getTamlRefId( pXmlElement ) != 0 || getTamlRefToId( pXmlElement ) != 0 )
  288. {
  289. // Yes, so parse proxy object.
  290. SimObject* pProxyObject = parseElement( pXmlElement );
  291. // Add child node.
  292. pCustomNode->addNode( pProxyObject );
  293. return;
  294. }
  295. // Yes, so add child node.
  296. TamlCustomNode* pChildNode = pCustomNode->addNode( pXmlElement->Value() );
  297. // Iterate attributes.
  298. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  299. {
  300. // Insert attribute name.
  301. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  302. // Skip if a Taml reference attribute.
  303. if ( attributeName == tamlRefIdName || attributeName == tamlRefToIdName )
  304. continue;
  305. // Add node field.
  306. pChildNode->addField( attributeName, pAttribute->Value() );
  307. }
  308. // Fetch any element text.
  309. const char* pElementText = pXmlElement->GetText();
  310. // Do we have any element text?
  311. if ( pElementText != NULL )
  312. {
  313. // Yes, so store it.
  314. pChildNode->setNodeText( pElementText );
  315. }
  316. // Fetch any children.
  317. tinyxml2::XMLNode* pChildXmlNode = pXmlElement->FirstChild();
  318. // Do we have any element children?
  319. if ( pChildXmlNode != NULL )
  320. {
  321. do
  322. {
  323. // Yes, so fetch child element.
  324. tinyxml2::XMLElement* pChildXmlElement = pChildXmlNode->ToElement();
  325. // Move to next sibling.
  326. pChildXmlNode = pChildXmlNode->NextSibling();
  327. // Skip if this is not an element.
  328. if ( pChildXmlElement == NULL )
  329. continue;
  330. // Parse custom node.
  331. parseCustomNode( pChildXmlElement, pChildNode );
  332. }
  333. while( pChildXmlNode != NULL );
  334. }
  335. }
  336. //-----------------------------------------------------------------------------
  337. U32 TamlXmlReader::getTamlRefId( tinyxml2::XMLElement* pXmlElement )
  338. {
  339. // Debug Profiling.
  340. PROFILE_SCOPE(TamlXmlReader_GetTamlRefId);
  341. // Iterate attributes.
  342. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  343. {
  344. // Insert attribute name.
  345. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  346. // Skip if not the correct attribute.
  347. if ( attributeName != tamlRefIdName )
  348. continue;
  349. // Return it.
  350. return dAtoi( pAttribute->Value() );
  351. }
  352. // Not found.
  353. return 0;
  354. }
  355. //-----------------------------------------------------------------------------
  356. U32 TamlXmlReader::getTamlRefToId( tinyxml2::XMLElement* pXmlElement )
  357. {
  358. // Debug Profiling.
  359. PROFILE_SCOPE(TamlXmlReader_GetTamlRefToId);
  360. // Iterate attributes.
  361. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  362. {
  363. // Insert attribute name.
  364. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  365. // Skip if not the correct attribute.
  366. if ( attributeName != tamlRefToIdName )
  367. continue;
  368. // Return it.
  369. return dAtoi( pAttribute->Value() );
  370. }
  371. // Not found.
  372. return 0;
  373. }
  374. //-----------------------------------------------------------------------------
  375. const char* TamlXmlReader::getTamlObjectName( tinyxml2::XMLElement* pXmlElement )
  376. {
  377. // Debug Profiling.
  378. PROFILE_SCOPE(TamlXmlReader_GetTamlObjectName);
  379. // Iterate attributes.
  380. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  381. {
  382. // Insert attribute name.
  383. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  384. // Skip if not the correct attribute.
  385. if ( attributeName != tamlNamedObjectName )
  386. continue;
  387. // Return it.
  388. return pAttribute->Value();
  389. }
  390. // Not found.
  391. return NULL;
  392. }