tamlXmlReader.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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. pSimObject->setFilename(mpTaml->getFilePathBuffer());
  96. // Find Taml callbacks.
  97. TamlCallbacks* pCallbacks = dynamic_cast<TamlCallbacks*>( pSimObject );
  98. // Are there any Taml callbacks?
  99. if ( pCallbacks != NULL )
  100. {
  101. // Yes, so call it.
  102. mpTaml->tamlPreRead( pCallbacks );
  103. }
  104. // Parse attributes.
  105. parseAttributes( pXmlElement, pSimObject );
  106. // Fetch object name.
  107. StringTableEntry objectName = StringTable->insert( getTamlObjectName( pXmlElement ) );
  108. // Does the object require a name?
  109. if ( objectName == StringTable->EmptyString() )
  110. {
  111. // No, so just register anonymously.
  112. pSimObject->registerObject();
  113. }
  114. else
  115. {
  116. // Yes, so register a named object.
  117. pSimObject->registerObject( objectName );
  118. // Was the name assigned?
  119. if ( pSimObject->getName() != objectName )
  120. {
  121. // No, so warn that the name was rejected.
  122. #ifdef TORQUE_DEBUG
  123. 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 );
  124. #else
  125. 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 );
  126. #endif
  127. }
  128. }
  129. // Do we have a reference Id?
  130. if ( tamlRefId != 0 )
  131. {
  132. // Yes, so insert reference.
  133. mObjectReferenceMap.insertUnique( tamlRefId, pSimObject );
  134. }
  135. // Fetch any children.
  136. tinyxml2::XMLNode* pChildXmlNode = pXmlElement->FirstChild();
  137. TamlCustomNodes customProperties;
  138. // Do we have any element children?
  139. if ( pChildXmlNode != NULL )
  140. {
  141. // Fetch the Taml children.
  142. TamlChildren* pChildren = dynamic_cast<TamlChildren*>( pSimObject );
  143. // Fetch any container child class specifier.
  144. AbstractClassRep* pContainerChildClass = pSimObject->getClassRep()->getContainerChildClass( true );
  145. // Iterate siblings.
  146. do
  147. {
  148. // Fetch element.
  149. tinyxml2::XMLElement* pChildXmlElement = pChildXmlNode->ToElement();
  150. // Move to next sibling.
  151. pChildXmlNode = pChildXmlNode->NextSibling();
  152. // Skip if this is not an element?
  153. if ( pChildXmlElement == NULL )
  154. continue;
  155. // Is this a standard child element?
  156. if ( dStrchr( pChildXmlElement->Value(), '.' ) == NULL )
  157. {
  158. // Is this a Taml child?
  159. if ( pChildren == NULL )
  160. {
  161. // No, so warn.
  162. Con::warnf("Taml: Child element '%s' found under parent '%s' but object cannot have children.",
  163. pChildXmlElement->Value(),
  164. pXmlElement->Value() );
  165. // Skip.
  166. continue;
  167. }
  168. // Yes, so parse child element.
  169. SimObject* pChildSimObject = parseElement( pChildXmlElement );
  170. // Skip if the child was not created.
  171. if ( pChildSimObject == NULL )
  172. continue;
  173. // Do we have a container child class?
  174. if ( pContainerChildClass != NULL )
  175. {
  176. // Yes, so is the child object the correctly derived type?
  177. if ( !pChildSimObject->getClassRep()->isClass( pContainerChildClass ) )
  178. {
  179. // No, so warn.
  180. Con::warnf("Taml: Child element '%s' found under parent '%s' but object is restricted to children of type '%s'.",
  181. pChildSimObject->getClassName(),
  182. pSimObject->getClassName(),
  183. pContainerChildClass->getClassName() );
  184. // NOTE: We can't delete the object as it may be referenced elsewhere!
  185. pChildSimObject = NULL;
  186. // Skip.
  187. continue;
  188. }
  189. }
  190. // Add child.
  191. pChildren->addTamlChild( pChildSimObject );
  192. // Find Taml callbacks for child.
  193. TamlCallbacks* pChildCallbacks = dynamic_cast<TamlCallbacks*>( pChildSimObject );
  194. // Do we have callbacks on the child?
  195. if ( pChildCallbacks != NULL )
  196. {
  197. // Yes, so perform callback.
  198. mpTaml->tamlAddParent( pChildCallbacks, pSimObject );
  199. }
  200. }
  201. else
  202. {
  203. // No, so parse custom element.
  204. parseCustomElement( pChildXmlElement, customProperties );
  205. }
  206. }
  207. while( pChildXmlNode != NULL );
  208. // Call custom read.
  209. mpTaml->tamlCustomRead( pCallbacks, customProperties );
  210. }
  211. // Are there any Taml callbacks?
  212. if ( pCallbacks != NULL )
  213. {
  214. // Yes, so call it.
  215. mpTaml->tamlPostRead( pCallbacks, customProperties );
  216. }
  217. // Return object.
  218. return pSimObject;
  219. }
  220. //-----------------------------------------------------------------------------
  221. void TamlXmlReader::parseAttributes( tinyxml2::XMLElement* pXmlElement, SimObject* pSimObject )
  222. {
  223. // Debug Profiling.
  224. PROFILE_SCOPE(TamlXmlReader_ParseAttributes);
  225. // Sanity!
  226. AssertFatal( pSimObject != NULL, "Taml: Cannot parse attributes on a NULL object." );
  227. // Iterate attributes.
  228. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  229. {
  230. // Insert attribute name.
  231. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  232. // Ignore if this is a Taml attribute.
  233. if ( attributeName == tamlRefIdName ||
  234. attributeName == tamlRefToIdName ||
  235. attributeName == tamlNamedObjectName )
  236. continue;
  237. //See if we have any sort of array index
  238. S32 suffixNum;
  239. String trimmedName = String::GetTrailingNumber(attributeName, suffixNum);
  240. if (!trimmedName.equal(attributeName))
  241. {
  242. char arrayIndexStr[32];
  243. dItoa(suffixNum, arrayIndexStr);
  244. // Set the field.
  245. pSimObject->setPrefixedDataField(StringTable->insert(trimmedName.c_str()), arrayIndexStr, pAttribute->Value());
  246. }
  247. else
  248. {
  249. // Set the field.
  250. pSimObject->setPrefixedDataField(attributeName, NULL, pAttribute->Value());
  251. }
  252. }
  253. }
  254. //-----------------------------------------------------------------------------
  255. void TamlXmlReader::parseCustomElement( tinyxml2::XMLElement* pXmlElement, TamlCustomNodes& customNodes )
  256. {
  257. // Debug Profiling.
  258. PROFILE_SCOPE(TamlXmlReader_ParseCustomElement);
  259. // Is this a standard child element?
  260. const char* pPeriod = dStrchr( pXmlElement->Value(), '.' );
  261. // Sanity!
  262. AssertFatal( pPeriod != NULL, "Parsing extended element but no period character found." );
  263. // Fetch any custom XML node.
  264. tinyxml2::XMLNode* pCustomXmlNode = pXmlElement->FirstChild();
  265. // Finish is no XML node exists.
  266. if ( pCustomXmlNode == NULL )
  267. return;
  268. // Yes, so add custom node.
  269. TamlCustomNode* pCustomNode = customNodes.addNode( pPeriod+1 );
  270. do
  271. {
  272. // Fetch element.
  273. tinyxml2::XMLElement* pCustomXmlElement = pCustomXmlNode->ToElement();
  274. // Move to next sibling.
  275. pCustomXmlNode = pCustomXmlNode->NextSibling();
  276. // Skip if this is not an element.
  277. if ( pCustomXmlElement == NULL )
  278. continue;
  279. // Parse custom node.
  280. parseCustomNode( pCustomXmlElement, pCustomNode );
  281. }
  282. while ( pCustomXmlNode != NULL );
  283. }
  284. //-----------------------------------------------------------------------------
  285. void TamlXmlReader::parseCustomNode( tinyxml2::XMLElement* pXmlElement, TamlCustomNode* pCustomNode )
  286. {
  287. // Is the node a proxy object?
  288. if ( getTamlRefId( pXmlElement ) != 0 || getTamlRefToId( pXmlElement ) != 0 )
  289. {
  290. // Yes, so parse proxy object.
  291. SimObject* pProxyObject = parseElement( pXmlElement );
  292. // Add child node.
  293. pCustomNode->addNode( pProxyObject );
  294. return;
  295. }
  296. // Yes, so add child node.
  297. TamlCustomNode* pChildNode = pCustomNode->addNode( pXmlElement->Value() );
  298. // Iterate attributes.
  299. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  300. {
  301. // Insert attribute name.
  302. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  303. // Skip if a Taml reference attribute.
  304. if ( attributeName == tamlRefIdName || attributeName == tamlRefToIdName )
  305. continue;
  306. // Add node field.
  307. pChildNode->addField( attributeName, pAttribute->Value() );
  308. }
  309. // Fetch any element text.
  310. const char* pElementText = pXmlElement->GetText();
  311. // Do we have any element text?
  312. if ( pElementText != NULL )
  313. {
  314. // Yes, so store it.
  315. pChildNode->setNodeText( pElementText );
  316. }
  317. // Fetch any children.
  318. tinyxml2::XMLNode* pChildXmlNode = pXmlElement->FirstChild();
  319. // Do we have any element children?
  320. if ( pChildXmlNode != NULL )
  321. {
  322. do
  323. {
  324. // Yes, so fetch child element.
  325. tinyxml2::XMLElement* pChildXmlElement = pChildXmlNode->ToElement();
  326. // Move to next sibling.
  327. pChildXmlNode = pChildXmlNode->NextSibling();
  328. // Skip if this is not an element.
  329. if ( pChildXmlElement == NULL )
  330. continue;
  331. // Parse custom node.
  332. parseCustomNode( pChildXmlElement, pChildNode );
  333. }
  334. while( pChildXmlNode != NULL );
  335. }
  336. }
  337. //-----------------------------------------------------------------------------
  338. U32 TamlXmlReader::getTamlRefId( tinyxml2::XMLElement* pXmlElement )
  339. {
  340. // Debug Profiling.
  341. PROFILE_SCOPE(TamlXmlReader_GetTamlRefId);
  342. // Iterate attributes.
  343. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  344. {
  345. // Insert attribute name.
  346. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  347. // Skip if not the correct attribute.
  348. if ( attributeName != tamlRefIdName )
  349. continue;
  350. // Return it.
  351. return dAtoi( pAttribute->Value() );
  352. }
  353. // Not found.
  354. return 0;
  355. }
  356. //-----------------------------------------------------------------------------
  357. U32 TamlXmlReader::getTamlRefToId( tinyxml2::XMLElement* pXmlElement )
  358. {
  359. // Debug Profiling.
  360. PROFILE_SCOPE(TamlXmlReader_GetTamlRefToId);
  361. // Iterate attributes.
  362. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  363. {
  364. // Insert attribute name.
  365. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  366. // Skip if not the correct attribute.
  367. if ( attributeName != tamlRefToIdName )
  368. continue;
  369. // Return it.
  370. return dAtoi( pAttribute->Value() );
  371. }
  372. // Not found.
  373. return 0;
  374. }
  375. //-----------------------------------------------------------------------------
  376. const char* TamlXmlReader::getTamlObjectName( tinyxml2::XMLElement* pXmlElement )
  377. {
  378. // Debug Profiling.
  379. PROFILE_SCOPE(TamlXmlReader_GetTamlObjectName);
  380. // Iterate attributes.
  381. for ( const tinyxml2::XMLAttribute* pAttribute = pXmlElement->FirstAttribute(); pAttribute; pAttribute = pAttribute->Next() )
  382. {
  383. // Insert attribute name.
  384. StringTableEntry attributeName = StringTable->insert( pAttribute->Name() );
  385. // Skip if not the correct attribute.
  386. if ( attributeName != tamlNamedObjectName )
  387. continue;
  388. // Return it.
  389. return pAttribute->Value();
  390. }
  391. // Not found.
  392. return NULL;
  393. }