engineXMLExport.cpp
Engine/source/console/engineXMLExport.cpp
A generator that will dump all export structures contained in an engine DLL to an XML file which may then be used by wrapper generators to create a language-specific binding for the engine API.
Public Defines
define
ADDRESS_TO_TYPE(tp) *( tp*)(addr);
define
PRIMTYPE(tp) ( < tp >() == type ) \ { \ tp val = (tp); \ = ( val ); \ }
Public Variables
const char *
Public Functions
DefineEngineFunction(exportEngineAPIToXML , SimXMLDocument * , () , "Create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> XML document containing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dump of the entire exported engine <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">API.\n\n</a>" "@return A <a href="/coding/class/classsimxmldocument/">SimXMLDocument</a> containing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dump of the engine's export information or <a href="/coding/file/typesx86unix_8h/#typesx86unix_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a> <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the operation <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">failed.\n\n</a>" "@ingroup Console" )
exportFunction(const EngineFunctionInfo * function, SimXMLDocument * xml)
exportScope(const EngineExportScope * scope, SimXMLDocument * xml, bool addNode)
exportType(const EngineTypeInfo * type, SimXMLDocument * xml)
getArgValue(const EngineFunctionDefaultArguments * defaultArgs, U32 idx)
getDefaultArgumentValue(const EngineFunctionInfo * function, const EngineTypeInfo * type, U32 idx)
const char *
getDocString(const EngineExport * exportInfo)
getTypeName(const EngineTypeInfo * type)
getValueForType(const EngineTypeInfo * type, void * addr)
bool
isExportFiltered(const EngineExport * exportInfo)
parseFunctionArgumentNames(const EngineFunctionInfo * function)
Helper to parse argument names out of a prototype string.
Detailed Description
A generator that will dump all export structures contained in an engine DLL to an XML file which may then be used by wrapper generators to create a language-specific binding for the engine API.
Using XML as an intermediary format allows the generators to use all of the export structures without actually having to access them directly in the DLL as native entities.
Public Defines
ADDRESS_TO_TYPE(tp) *( tp*)(addr);
PRIMTYPE(tp) ( < tp >() == type ) \ { \ tp val = (tp); \ = ( val ); \ }
Public Variables
const char * sExportFilterList []
Public Functions
DefineEngineFunction(exportEngineAPIToXML , SimXMLDocument * , () , "Create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> XML document containing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dump of the entire exported engine <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">API.\n\n</a>" "@return A <a href="/coding/class/classsimxmldocument/">SimXMLDocument</a> containing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> dump of the engine's export information or <a href="/coding/file/typesx86unix_8h/#typesx86unix_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a> <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the operation <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">failed.\n\n</a>" "@ingroup Console" )
exportFunction(const EngineFunctionInfo * function, SimXMLDocument * xml)
exportScope(const EngineExportScope * scope, SimXMLDocument * xml, bool addNode)
exportType(const EngineTypeInfo * type, SimXMLDocument * xml)
getArgValue(const EngineFunctionDefaultArguments * defaultArgs, U32 idx)
getDefaultArgumentValue(const EngineFunctionInfo * function, const EngineTypeInfo * type, U32 idx)
getDocString(const EngineExport * exportInfo)
getTypeName(const EngineTypeInfo * type)
getValueForType(const EngineTypeInfo * type, void * addr)
isExportFiltered(const EngineExport * exportInfo)
parseFunctionArgumentNames(const EngineFunctionInfo * function)
Helper to parse argument names out of a prototype string.
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "console/engineExports.h" 25#include "console/engineAPI.h" 26#include "console/engineTypes.h" 27#include "console/engineFunctions.h" 28#include "console/SimXMLDocument.h" 29 30 31/// @file 32/// A generator that will dump all export structures contained in an engine 33/// DLL to an XML file which may then be used by wrapper generators to create a 34/// language-specific binding for the engine API. Using XML as an intermediary 35/// format allows the generators to use all of the export structures without 36/// actually having to access them directly in the DLL as native entities. 37 38 39static void exportScope(const EngineExportScope* scope, SimXMLDocument* xml, bool addNode = false); 40 41 42static String getTypeName(const EngineTypeInfo* type) 43{ 44 if (!type) 45 { 46 static String sVoid("void"); 47 return sVoid; 48 } 49 50 return type->getFullyQualifiedExportName(); 51} 52 53static const char* getDocString(const EngineExport* exportInfo) 54{ 55 if (!exportInfo->getDocString()) 56 return ""; 57 58 return exportInfo->getDocString(); 59} 60 61template< typename T > 62inline T getArgValue(const EngineFunctionDefaultArguments* defaultArgs, U32 idx) 63{ 64 return *(const T*)(defaultArgs->mFirst + defaultArgs->mOffsets[idx]); 65} 66 67 68// List of exports that we want filtered out. This will only be needed as long 69// as the console system is still around. 70static const char* sExportFilterList[] = 71{ 72 "Console", // Console namespace 73}; 74 75static bool isExportFiltered(const EngineExport* exportInfo) 76{ 77 String qualifiedName = exportInfo->getFullyQualifiedExportName(); 78 79 for (U32 i = 0; i < (sizeof(sExportFilterList) / sizeof(sExportFilterList[0])); ++i) 80 if (qualifiedName.compare(sExportFilterList[i]) == 0) 81 return true; 82 83 return false; 84} 85 86//============================================================================= 87// Functions. 88//============================================================================= 89// MARK: ---- Functions ---- 90 91//----------------------------------------------------------------------------- 92 93/// Helper to parse argument names out of a prototype string. 94static Vector< String> parseFunctionArgumentNames(const EngineFunctionInfo* function) 95{ 96 Vector< String> argNames; 97 98 const char* prototype = function->getPrototypeString(); 99 if (!prototype) 100 return argNames; 101 102 const U32 prototypeLength = dStrlen(prototype); 103 const char* prototypeEnd = &prototype[prototypeLength]; 104 const char* ptr = prototypeEnd - 1; 105 106 // Search for right parenthesis. 107 while (ptr >= prototype && *ptr != ')') 108 ptr--; 109 110 if (ptr < prototype) 111 return argNames; 112 ptr--; 113 114 while (ptr >= prototype && *ptr != '(') 115 { 116 // Skip back over spaces. 117 118 while (ptr >= prototype && dIsspace(*ptr)) 119 ptr--; 120 if (ptr < prototype) 121 return argNames; 122 123 // Parse out name. 124 125 const char* end = ptr + 1; 126 while (ptr > prototype && (dIsalnum(*ptr) || *ptr == '_')) 127 ptr--; 128 const char* start = ptr + 1; 129 130 // Skip back over spaces. 131 132 while (ptr >= prototype && dIsspace(*ptr)) 133 ptr--; 134 135 // If we're sure we don't have just a type name without an 136 // argument name, copy out the argument name name. 137 138 if (ptr >= prototype && *ptr != ',' && *ptr != '(' && end > start) 139 argNames.push_front(String(start, end - start)); 140 else 141 argNames.push_front(""); 142 143 // Skip back to comma or opening parenthesis. 144 145 U32 parenNestingCount = 0; 146 while (ptr >= prototype) 147 { 148 if (*ptr == ')') 149 parenNestingCount++; 150 else if (*ptr == '(') 151 parenNestingCount--; 152 else if (*ptr == ',' && parenNestingCount == 0) 153 { 154 ptr--; 155 break; 156 } 157 else if (*ptr == '(' && parenNestingCount == 0) 158 break; 159 160 ptr--; 161 } 162 } 163 164 // Add 'this' parameter if this is a method. 165 166 if (dStrncmp(prototype, "virtual ", sizeof("virtual ") - 1) == 0) 167 argNames.push_front("this"); 168 169 return argNames; 170} 171 172//----------------------------------------------------------------------------- 173static String getValueForType(const EngineTypeInfo* type, void* addr) 174{ 175 String value; 176#define ADDRESS_TO_TYPE(tp) *(const tp*)(addr); 177 178 switch (type->getTypeKind()) 179 { 180 case EngineTypeKindPrimitive: 181 { 182#define PRIMTYPE( tp ) \ 183 if( TYPE< tp >() == type ) \ 184 { \ 185 tp val = ADDRESS_TO_TYPE(tp); \ 186 value = String::ToString( val ); \ 187 } 188 189 PRIMTYPE(bool); 190 PRIMTYPE(S8); 191 PRIMTYPE(U8); 192 PRIMTYPE(S32); 193 PRIMTYPE(U32); 194 PRIMTYPE(F32); 195 PRIMTYPE(F64); 196 197 //TODO: for now we store string literals in ASCII; needs to be sorted out 198 if (TYPE< String >() == type || TYPE< const UTF8* >() == type) 199 { 200 const UTF8* val = *((const UTF8**)(addr)); 201 value = val; 202 } 203 204#undef PRIMTYPE 205 break; 206 } 207 208 case EngineTypeKindEnum: 209 { 210 S32 val = ADDRESS_TO_TYPE(S32); 211 AssertFatal(type->getEnumTable(), "engineXMLExport - Enum type without table!"); 212 213 const EngineEnumTable& table = *(type->getEnumTable()); 214 const U32 numValues = table.getNumValues(); 215 216 for (U32 i = 0; i < numValues; ++i) 217 if (table[i].getInt() == val) 218 { 219 value = table[i].getName(); 220 break; 221 } 222 223 break; 224 } 225 226 case EngineTypeKindBitfield: 227 { 228 S32 val = ADDRESS_TO_TYPE(S32); 229 AssertFatal(type->getEnumTable(), "engineXMLExport - Bitfield type without table!"); 230 231 const EngineEnumTable& table = *(type->getEnumTable()); 232 const U32 numValues = table.getNumValues(); 233 234 bool isFirst = true; 235 for (U32 i = 0; i < numValues; ++i) 236 if (table[i].getInt() & val) 237 { 238 if (!isFirst) 239 value += '|'; 240 241 value = table[i].getName(); 242 isFirst = false; 243 } 244 245 break; 246 } 247 248 case EngineTypeKindStruct: 249 { 250 AssertFatal(type->getFieldTable(), "engineXMLExport - Struct type without table!"); 251 const EngineFieldTable* fieldTable = type->getFieldTable(); 252 U32 numFields = fieldTable->getNumFields(); 253 254 255 for (int i = 0; i < numFields; ++i) 256 { 257 const EngineTypeInfo* fieldType = (*fieldTable)[i].getType(); 258 U32 fieldOffset = (*fieldTable)[i].getOffset(); 259 U32 numElements = (*fieldTable)[i].getNumElements(); 260 261 for (int j = 0; j < numElements; ++j) 262 { 263 if (i == 0 && j == 0) { 264 value = getValueForType(fieldType, (void*)((size_t)addr + fieldOffset)); 265 } 266 else { 267 value += " " + getValueForType(fieldType, (void*)((size_t)addr + (size_t)fieldOffset * ((size_t)j * fieldType->getInstanceSize()))); 268 } 269 } 270 } 271 272 break; 273 } 274 275 case EngineTypeKindClass: 276 case EngineTypeKindFunction: 277 { 278 // For these two kinds, we support "null" as the only valid 279 // default value. 280 281 const void* ptr = ADDRESS_TO_TYPE(void*); 282 if (!ptr) 283 value = "null"; 284 break; 285 } 286 287 default: 288 break; 289 } 290 291#undef ADDRESS_TO_TYPE 292 return value; 293} 294 295//----------------------------------------------------------------------------- 296 297static String getDefaultArgumentValue(const EngineFunctionInfo* function, const EngineTypeInfo* type, U32 idx) 298{ 299 const EngineFunctionDefaultArguments* defaultArgs = function->getDefaultArguments(); 300 return getValueForType(type, (void*)(defaultArgs->mFirst + defaultArgs->mOffsets[idx])); 301} 302 303//----------------------------------------------------------------------------- 304 305static void exportFunction(const EngineFunctionInfo* function, SimXMLDocument* xml) 306{ 307 if (isExportFiltered(function)) 308 return; 309 310 xml->pushNewElement("EngineFunction"); 311 312 xml->setAttribute("name", function->getExportName()); 313 xml->setAttribute("returnType", getTypeName(function->getReturnType())); 314 xml->setAttribute("symbol", function->getBindingName()); 315 xml->setAttribute("isCallback", function->isCallout() ? "1" : "0"); 316 xml->setAttribute("isVariadic", function->getFunctionType()->isVariadic() ? "1" : "0"); 317 xml->setAttribute("docs", getDocString(function)); 318 319 xml->pushNewElement("arguments"); 320 321 const U32 numArguments = function->getNumArguments(); 322 const U32 numDefaultArguments = (function->getDefaultArguments() ? function->getDefaultArguments()->mNumDefaultArgs : 0); 323 const U32 firstDefaultArg = numArguments - numDefaultArguments; 324 325 Vector< String> argumentNames = parseFunctionArgumentNames(function); 326 const U32 numArgumentNames = argumentNames.size(); 327 328 for (U32 i = 0; i < numArguments; ++i) 329 { 330 xml->pushNewElement("EngineFunctionArgument"); 331 const EngineTypeInfo* type = function->getArgumentType(i); 332 AssertFatal(type != NULL, "exportFunction - Argument cannot have type void!"); 333 334 String argName; 335 if (i < numArgumentNames) 336 argName = argumentNames[i]; 337 338 xml->setAttribute("name", argName); 339 xml->setAttribute("type", getTypeName(type)); 340 341 if (i >= firstDefaultArg) 342 { 343 String defaultValue = getDefaultArgumentValue(function, type, i); 344 xml->setAttribute("defaultValue", defaultValue); 345 } 346 347 // A bit hacky, default arguments have all offsets. 348 if (function->getDefaultArguments() != NULL) 349 { 350 xml->setAttribute("offset", String::ToString(function->getDefaultArguments()->mOffsets[i])); 351 } 352 353 xml->popElement(); 354 } 355 356 xml->popElement(); 357 358 xml->popElement(); 359} 360 361 362//============================================================================= 363// Types. 364//============================================================================= 365// MARK: ---- Types ---- 366 367//----------------------------------------------------------------------------- 368 369static void exportType(const EngineTypeInfo* type, SimXMLDocument* xml) 370{ 371 // Don't export anonymous types. 372 if (!type->getTypeName()[0]) 373 return; 374 375 if (isExportFiltered(type)) 376 return; 377 378 const char* nodeName = NULL; 379 switch (type->getTypeKind()) 380 { 381 case EngineTypeKindPrimitive: 382 nodeName = "EnginePrimitiveType"; 383 break; 384 385 case EngineTypeKindEnum: 386 nodeName = "EngineEnumType"; 387 break; 388 389 case EngineTypeKindBitfield: 390 nodeName = "EngineBitfieldType"; 391 break; 392 393 case EngineTypeKindStruct: 394 nodeName = "EngineStructType"; 395 break; 396 397 case EngineTypeKindClass: 398 nodeName = "EngineClassType"; 399 break; 400 401 default: 402 return; 403 } 404 405 xml->pushNewElement(nodeName); 406 407 xml->setAttribute("name", type->getTypeName()); 408 xml->setAttribute("size", String::ToString(type->getInstanceSize())); 409 xml->setAttribute("isAbstract", type->isAbstract() ? "1" : "0"); 410 xml->setAttribute("isInstantiable", type->isInstantiable() ? "1" : "0"); 411 xml->setAttribute("isDisposable", type->isDisposable() ? "1" : "0"); 412 xml->setAttribute("isSingleton", type->isSingleton() ? "1" : "0"); 413 xml->setAttribute("docs", getDocString(type)); 414 415 if (type->getSuperType()) 416 xml->setAttribute("superType", getTypeName(type->getSuperType())); 417 418 if (type->getEnumTable()) 419 { 420 xml->pushNewElement("enums"); 421 422 const EngineEnumTable& table = *(type->getEnumTable()); 423 const U32 numValues = table.getNumValues(); 424 425 for (U32 i = 0; i < numValues; ++i) 426 { 427 xml->pushNewElement("EngineEnum"); 428 429 xml->setAttribute("name", table[i].getName()); 430 xml->setAttribute("value", String::ToString(table[i].getInt())); 431 xml->setAttribute("docs", table[i].getDocString() ? table[i].getDocString() : ""); 432 433 xml->popElement(); 434 } 435 436 xml->popElement(); 437 } 438 else if (type->getFieldTable()) 439 { 440 xml->pushNewElement("fields"); 441 442 const EngineFieldTable& table = *(type->getFieldTable()); 443 const U32 numFields = table.getNumFields(); 444 445 for (U32 i = 0; i < numFields; ++i) 446 { 447 const EngineFieldTable::Field& field = table[i]; 448 449 xml->pushNewElement("EngineField"); 450 451 xml->setAttribute("name", field.getName()); 452 xml->setAttribute("type", getTypeName(field.getType())); 453 xml->setAttribute("offset", String::ToString(field.getOffset())); 454 xml->setAttribute("indexedSize", String::ToString(field.getNumElements())); 455 xml->setAttribute("docs", field.getDocString() ? field.getDocString() : ""); 456 457 xml->popElement(); 458 } 459 460 xml->popElement(); 461 } 462 else if (type->getPropertyTable()) 463 { 464 xml->pushNewElement("properties"); 465 466 const EnginePropertyTable& table = *(type->getPropertyTable()); 467 const U32 numProperties = table.getNumProperties(); 468 U32 groupNestingDepth = 0; 469 470 for (U32 i = 0; i < numProperties; ++i) 471 { 472 const EnginePropertyTable::Property& property = table[i]; 473 474 if (property.isGroupBegin()) 475 { 476 groupNestingDepth++; 477 xml->pushNewElement("EnginePropertyGroup"); 478 479 xml->setAttribute("name", property.getName()); 480 xml->setAttribute("indexedSize", String::ToString(property.getNumElements())); 481 xml->setAttribute("docs", property.getDocString() ? property.getDocString() : ""); 482 483 xml->pushNewElement("properties"); 484 } 485 else if (property.isGroupEnd()) 486 { 487 groupNestingDepth--; 488 xml->popElement(); 489 xml->popElement(); 490 } 491 else 492 { 493 if (property.getType() == AbstractClassRep::StartArrayFieldType 494 || property.getType() == AbstractClassRep::EndArrayFieldType) { 495 continue; 496 } 497 xml->pushNewElement("EngineProperty"); 498 499 xml->setAttribute("name", property.getName()); 500 xml->setAttribute("indexedSize", String::ToString(property.getNumElements())); 501 xml->setAttribute("isConstant", property.isConstant() ? "1" : "0"); 502 xml->setAttribute("isTransient", property.isTransient() ? "1" : "0"); 503 xml->setAttribute("isVisible", property.hideInInspectors() ? "0" : "1"); 504 xml->setAttribute("docs", property.getDocString() ? property.getDocString() : ""); 505 506 507 const bool isDeprecated = (property.getType() == AbstractClassRep::DeprecatedFieldType); 508 509 if (isDeprecated) 510 { 511 xml->setAttribute("type", "deprecated"); 512 } 513 else 514 { 515 ConsoleBaseType *cbt = ConsoleBaseType::getType(property.getType()); 516 if (cbt != NULL) 517 { 518 if (cbt->getTypeInfo() != NULL) { 519 xml->setAttribute("type", cbt->getTypeInfo()->getTypeName()); 520 } 521 else { 522 xml->setAttribute("type", cbt->getTypeClassName()); 523 } 524 } 525 else 526 { 527 xml->setAttribute("type", "unknown"); 528 } 529 } 530 531 xml->popElement(); 532 } 533 } 534 535 AssertFatal(!groupNestingDepth, "exportType - Property group nesting mismatch!"); 536 xml->popElement(); 537 } 538 exportScope(type, xml); 539 540 xml->popElement(); 541} 542 543 544//============================================================================= 545// Scopes. 546//============================================================================= 547// MARK: ---- Scopes ---- 548 549//----------------------------------------------------------------------------- 550 551static void exportScope(const EngineExportScope* scope, SimXMLDocument* xml, bool addNode) 552{ 553 if (addNode) 554 { 555 if (isExportFiltered(scope)) 556 return; 557 558 xml->pushNewElement("EngineExportScope"); 559 560 xml->setAttribute("name", scope->getExportName()); 561 xml->setAttribute("docs", getDocString(scope)); 562 } 563 564 // Dump all contained exports. 565 566 xml->pushNewElement("exports"); 567 568 for (const EngineExport* exportInfo = scope->getExports(); exportInfo != NULL; exportInfo = exportInfo->getNextExport()) 569 { 570 switch (exportInfo->getExportKind()) 571 { 572 case EngineExportKindScope: 573 exportScope(static_cast< const EngineExportScope* >(exportInfo), xml, true); 574 break; 575 576 case EngineExportKindFunction: 577 exportFunction(static_cast< const EngineFunctionInfo* >(exportInfo), xml); 578 break; 579 580 case EngineExportKindType: 581 exportType(static_cast< const EngineTypeInfo* >(exportInfo), xml); 582 break; 583 584 default: 585 AssertFatal(true, "Unknown EngineExportKind: " + exportInfo->getExportKind()); 586 break; 587 } 588 } 589 590 xml->popElement(); 591 592 if (addNode) 593 xml->popElement(); 594} 595 596//----------------------------------------------------------------------------- 597 598DefineEngineFunction(exportEngineAPIToXML, SimXMLDocument*, (), , 599 "Create a XML document containing a dump of the entire exported engine API.\n\n" 600 "@return A SimXMLDocument containing a dump of the engine's export information or NULL if the operation failed.\n\n" 601 "@ingroup Console") 602{ 603 SimXMLDocument* xml = new SimXMLDocument; 604 xml->registerObject(); 605 Sim::getRootGroup()->addObject(xml); 606 xml->addHeader(); 607 608 exportScope(EngineExportScope::getGlobalScope(), xml, true); 609 610 return xml; 611} 612