tinyxml2.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. #include "tinyxml2.h"
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <ctype.h>
  6. #include <new.h>
  7. //#pragma warning ( disable : 4291 )
  8. using namespace tinyxml2;
  9. static const char LINE_FEED = (char)0x0a; // all line endings are normalized to LF
  10. static const char LF = LINE_FEED;
  11. static const char CARRIAGE_RETURN = (char)0x0d; // CR gets filtered out
  12. static const char CR = CARRIAGE_RETURN;
  13. static const char SINGLE_QUOTE = '\'';
  14. static const char DOUBLE_QUOTE = '\"';
  15. struct Entity {
  16. const char* pattern;
  17. int length;
  18. char value;
  19. };
  20. static const int NUM_ENTITIES = 5;
  21. static const Entity entities[NUM_ENTITIES] =
  22. {
  23. { "quot", 4, DOUBLE_QUOTE },
  24. { "amp", 3, '&' },
  25. { "apos", 4, SINGLE_QUOTE },
  26. { "lt", 2, '<' },
  27. { "gt", 2, '>' }
  28. };
  29. const char* StrPair::GetStr()
  30. {
  31. if ( flags & NEEDS_FLUSH ) {
  32. *end = 0;
  33. flags ^= NEEDS_FLUSH;
  34. if ( flags ) {
  35. char* p = start;
  36. char* q = start;
  37. while( p < end ) {
  38. if ( (flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR ) {
  39. // CR-LF pair becomes LF
  40. // CR alone becomes LF
  41. // LF-CR becomes LF
  42. if ( *(p+1) == LF ) {
  43. p += 2;
  44. }
  45. else {
  46. ++p;
  47. }
  48. *q = LF;
  49. }
  50. else if ( (flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF ) {
  51. if ( *(p+1) == CR ) {
  52. p += 2;
  53. }
  54. else {
  55. ++p;
  56. }
  57. *q = LF;
  58. }
  59. else if ( (flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) {
  60. int i=0;
  61. for( i=0; i<NUM_ENTITIES; ++i ) {
  62. if ( strncmp( p+1, entities[i].pattern, entities[i].length ) == 0
  63. && *(p+entities[i].length+1) == ';' )
  64. {
  65. // Found an entity convert;
  66. *q = entities[i].value;
  67. ++q;
  68. p += entities[i].length + 2;
  69. break;
  70. }
  71. }
  72. if ( i == NUM_ENTITIES ) {
  73. // fixme: treat as error?
  74. ++p;
  75. ++q;
  76. }
  77. }
  78. else {
  79. *q = *p;
  80. ++p;
  81. ++q;
  82. }
  83. }
  84. *q = 0;
  85. }
  86. flags = 0;
  87. }
  88. return start;
  89. }
  90. /*
  91. const char* StringPool::Intern( const char* str )
  92. {
  93. // Treat the array as a linear, inplace hash table.
  94. // Nothing can get deleted, so that's handy.
  95. if ( size > pool.Size()*3/4 ) {
  96. DynArray< const char*, 20 > store;
  97. for( int i=0; i<pool.Size(); ++i ) {
  98. if ( pool[i] != 0 ) {
  99. store.Push( pool[i] );
  100. }
  101. }
  102. int newSize = pool.Size() * 2;
  103. pool.PopArr( pool.Size() );
  104. const char** mem = pool.PushArr( newSize );
  105. memset( (void*)mem, 0, sizeof(char)*newSize );
  106. while ( !store.Empty() ) {
  107. Intern( store.Pop() );
  108. }
  109. }
  110. }
  111. */
  112. // --------- XMLBase ----------- //
  113. char* XMLBase::ParseText( char* p, StrPair* pair, const char* endTag, int strFlags )
  114. {
  115. TIXMLASSERT( endTag && *endTag );
  116. char* start = p;
  117. char endChar = *endTag;
  118. int length = strlen( endTag );
  119. // Inner loop of text parsing.
  120. while ( *p ) {
  121. if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) {
  122. pair->Set( start, p, strFlags );
  123. return p + length;
  124. }
  125. ++p;
  126. }
  127. return p;
  128. }
  129. char* XMLBase::ParseName( char* p, StrPair* pair )
  130. {
  131. char* start = p;
  132. start = p;
  133. if ( !start || !(*start) ) {
  134. return 0;
  135. }
  136. if ( !IsAlpha( *p ) ) {
  137. return 0;
  138. }
  139. while( *p && (
  140. IsAlphaNum( (unsigned char) *p )
  141. || *p == '_'
  142. || *p == '-'
  143. || *p == '.'
  144. || *p == ':' ))
  145. {
  146. ++p;
  147. }
  148. if ( p > start ) {
  149. pair->Set( start, p, 0 );
  150. return p;
  151. }
  152. return 0;
  153. }
  154. char* XMLDocument::Identify( char* p, XMLNode** node )
  155. {
  156. XMLNode* returnNode = 0;
  157. char* start = p;
  158. p = XMLBase::SkipWhiteSpace( p );
  159. if( !p || !*p )
  160. {
  161. return 0;
  162. }
  163. // What is this thing?
  164. // - Elements start with a letter or underscore, but xml is reserved.
  165. // - Comments: <!--
  166. // - Decleration: <?xml
  167. // - Everthing else is unknown to tinyxml.
  168. //
  169. static const char* xmlHeader = { "<?xml" };
  170. static const char* commentHeader = { "<!--" };
  171. static const char* dtdHeader = { "<!" };
  172. static const char* cdataHeader = { "<![CDATA[" };
  173. static const char* elementHeader = { "<" }; // and a header for everything else; check last.
  174. static const int xmlHeaderLen = 5;
  175. static const int commentHeaderLen = 4;
  176. static const int dtdHeaderLen = 2;
  177. static const int cdataHeaderLen = 9;
  178. static const int elementHeaderLen = 1;
  179. if ( XMLBase::StringEqual( p, commentHeader, commentHeaderLen ) ) {
  180. returnNode = new (commentPool.Alloc()) XMLComment( this );
  181. returnNode->memPool = &commentPool;
  182. p += commentHeaderLen;
  183. }
  184. else if ( XMLBase::StringEqual( p, elementHeader, elementHeaderLen ) ) {
  185. returnNode = new (elementPool.Alloc()) XMLElement( this );
  186. returnNode->memPool = &elementPool;
  187. p += elementHeaderLen;
  188. }
  189. // fixme: better text detection
  190. else if ( (*p != '<') && XMLBase::IsAlphaNum( *p ) ) {
  191. returnNode = new (textPool.Alloc()) XMLText( this );
  192. returnNode->memPool = &textPool;
  193. p = start; // Back it up, all the text counts.
  194. }
  195. else {
  196. TIXMLASSERT( 0 );
  197. }
  198. *node = returnNode;
  199. return p;
  200. }
  201. // --------- XMLNode ----------- //
  202. XMLNode::XMLNode( XMLDocument* doc ) :
  203. document( doc ),
  204. parent( 0 ),
  205. isTextParent( false ),
  206. firstChild( 0 ), lastChild( 0 ),
  207. prev( 0 ), next( 0 )
  208. {
  209. }
  210. XMLNode::~XMLNode()
  211. {
  212. ClearChildren();
  213. if ( parent ) {
  214. parent->Unlink( this );
  215. }
  216. }
  217. void XMLNode::ClearChildren()
  218. {
  219. while( firstChild ) {
  220. XMLNode* node = firstChild;
  221. Unlink( node );
  222. //delete node;
  223. // placement new!
  224. MemPool* pool = node->memPool;
  225. node->~XMLNode();
  226. pool->Free( node );
  227. // fixme: memory never free'd.
  228. }
  229. firstChild = lastChild = 0;
  230. }
  231. void XMLNode::Unlink( XMLNode* child )
  232. {
  233. TIXMLASSERT( child->parent == this );
  234. if ( child == firstChild )
  235. firstChild = firstChild->next;
  236. if ( child == lastChild )
  237. lastChild = lastChild->prev;
  238. if ( child->prev ) {
  239. child->prev->next = child->next;
  240. }
  241. if ( child->next ) {
  242. child->next->prev = child->prev;
  243. }
  244. child->parent = 0;
  245. }
  246. XMLNode* XMLNode::InsertEndChild( XMLNode* addThis )
  247. {
  248. if ( lastChild ) {
  249. TIXMLASSERT( firstChild );
  250. TIXMLASSERT( lastChild->next == 0 );
  251. lastChild->next = addThis;
  252. addThis->prev = lastChild;
  253. lastChild = addThis;
  254. addThis->parent = this;
  255. addThis->next = 0;
  256. }
  257. else {
  258. TIXMLASSERT( firstChild == 0 );
  259. firstChild = lastChild = addThis;
  260. addThis->parent = this;
  261. addThis->prev = 0;
  262. addThis->next = 0;
  263. }
  264. if ( addThis->ToText() ) {
  265. SetTextParent();
  266. }
  267. return addThis;
  268. }
  269. XMLElement* XMLNode::FirstChildElement( const char* value )
  270. {
  271. for( XMLNode* node=firstChild; node; node=node->next ) {
  272. XMLElement* element = node->ToElement();
  273. if ( element ) {
  274. if ( !value || XMLBase::StringEqual( element->Name(), value ) ) {
  275. return element;
  276. }
  277. }
  278. }
  279. return 0;
  280. }
  281. void XMLNode::Print( XMLStreamer* streamer )
  282. {
  283. for( XMLNode* node = firstChild; node; node=node->next ) {
  284. node->Print( streamer );
  285. }
  286. }
  287. char* XMLNode::ParseDeep( char* p )
  288. {
  289. while( p && *p ) {
  290. XMLNode* node = 0;
  291. p = document->Identify( p, &node );
  292. if ( p && node ) {
  293. p = node->ParseDeep( p );
  294. // FIXME: is it the correct closing element?
  295. if ( node->IsClosingElement() ) {
  296. //delete node;
  297. MemPool* pool = node->memPool;
  298. node->~XMLNode(); // fixme linked list memory not free
  299. pool->Free( node );
  300. return p;
  301. }
  302. this->InsertEndChild( node );
  303. }
  304. }
  305. return 0;
  306. }
  307. // --------- XMLText ---------- //
  308. char* XMLText::ParseDeep( char* p )
  309. {
  310. p = XMLBase::ParseText( p, &value, "<", StrPair::TEXT_ELEMENT );
  311. // consumes the end tag.
  312. if ( p && *p ) {
  313. return p-1;
  314. }
  315. return 0;
  316. }
  317. void XMLText::Print( XMLStreamer* streamer )
  318. {
  319. const char* v = value.GetStr();
  320. streamer->PushText( v );
  321. }
  322. // --------- XMLComment ---------- //
  323. XMLComment::XMLComment( XMLDocument* doc ) : XMLNode( doc )
  324. {
  325. }
  326. XMLComment::~XMLComment()
  327. {
  328. //printf( "~XMLComment\n" );
  329. }
  330. void XMLComment::Print( XMLStreamer* streamer )
  331. {
  332. // XMLNode::Print( fp, depth );
  333. // fprintf( fp, "<!--%s-->\n", value.GetStr() );
  334. streamer->PushComment( value.GetStr() );
  335. }
  336. char* XMLComment::ParseDeep( char* p )
  337. {
  338. // Comment parses as text.
  339. return XMLBase::ParseText( p, &value, "-->", StrPair::COMMENT );
  340. }
  341. // --------- XMLAttribute ---------- //
  342. char* XMLAttribute::ParseDeep( char* p )
  343. {
  344. p = XMLBase::ParseText( p, &name, "=", StrPair::ATTRIBUTE_NAME );
  345. if ( !p || !*p ) return 0;
  346. char endTag[2] = { *p, 0 };
  347. ++p;
  348. p = XMLBase::ParseText( p, &value, endTag, StrPair::ATTRIBUTE_VALUE );
  349. if ( value.Empty() ) return 0;
  350. return p;
  351. }
  352. void XMLAttribute::Print( XMLStreamer* streamer )
  353. {
  354. // fixme: sort out single vs. double quote
  355. //fprintf( cfile, "%s=\"%s\"", name.GetStr(), value.GetStr() );
  356. streamer->PushAttribute( name.GetStr(), value.GetStr() );
  357. }
  358. // --------- XMLElement ---------- //
  359. XMLElement::XMLElement( XMLDocument* doc ) : XMLNode( doc ),
  360. closing( false ),
  361. rootAttribute( 0 ),
  362. lastAttribute( 0 )
  363. {
  364. }
  365. XMLElement::~XMLElement()
  366. {
  367. //printf( "~XMLElemen %x\n",this );
  368. XMLAttribute* attribute = rootAttribute;
  369. while( attribute ) {
  370. XMLAttribute* next = attribute->next;
  371. delete attribute;
  372. attribute = next;
  373. }
  374. }
  375. char* XMLElement::ParseAttributes( char* p, bool* closedElement )
  376. {
  377. const char* start = p;
  378. *closedElement = false;
  379. // Read the attributes.
  380. while( p ) {
  381. p = XMLBase::SkipWhiteSpace( p );
  382. if ( !p || !(*p) ) {
  383. document->SetError( XMLDocument::ERROR_PARSING_ELEMENT, start, Name() );
  384. return 0;
  385. }
  386. // attribute.
  387. if ( XMLBase::IsAlpha( *p ) ) {
  388. XMLAttribute* attrib = new XMLAttribute( this );
  389. p = attrib->ParseDeep( p );
  390. if ( !p ) {
  391. delete attrib;
  392. document->SetError( XMLDocument::ERROR_PARSING_ATTRIBUTE, start, p );
  393. return 0;
  394. }
  395. if ( rootAttribute ) {
  396. TIXMLASSERT( lastAttribute );
  397. lastAttribute->next = attrib;
  398. lastAttribute = attrib;
  399. }
  400. else {
  401. rootAttribute = lastAttribute = attrib;
  402. }
  403. }
  404. // end of the tag
  405. else if ( *p == '/' && *(p+1) == '>' ) {
  406. if ( closing ) {
  407. document->SetError( XMLDocument::ERROR_PARSING_ELEMENT, start, p );
  408. return 0;
  409. }
  410. *closedElement = true;
  411. return p+2; // done; sealed element.
  412. }
  413. // end of the tag
  414. else if ( *p == '>' ) {
  415. ++p;
  416. break;
  417. }
  418. else {
  419. document->SetError( XMLDocument::ERROR_PARSING_ELEMENT, start, p );
  420. return 0;
  421. }
  422. }
  423. return p;
  424. }
  425. //
  426. // <ele></ele>
  427. // <ele>foo<b>bar</b></ele>
  428. //
  429. char* XMLElement::ParseDeep( char* p )
  430. {
  431. // Read the element name.
  432. p = XMLBase::SkipWhiteSpace( p );
  433. if ( !p ) return 0;
  434. const char* start = p;
  435. // The closing element is the </element> form. It is
  436. // parsed just like a regular element then deleted from
  437. // the DOM.
  438. if ( *p == '/' ) {
  439. closing = true;
  440. ++p;
  441. }
  442. p = XMLBase::ParseName( p, &value );
  443. if ( value.Empty() ) return 0;
  444. bool elementClosed=false;
  445. p = ParseAttributes( p, &elementClosed );
  446. if ( !p || !*p || elementClosed || closing )
  447. return p;
  448. p = XMLNode::ParseDeep( p );
  449. return p;
  450. }
  451. void XMLElement::Print( XMLStreamer* streamer )
  452. {
  453. //if ( !parent || !parent->IsTextParent() ) {
  454. // PrintSpace( cfile, depth );
  455. //}
  456. //fprintf( cfile, "<%s", Name() );
  457. streamer->OpenElement( Name(), IsTextParent() );
  458. for( XMLAttribute* attrib=rootAttribute; attrib; attrib=attrib->next ) {
  459. //fprintf( cfile, " " );
  460. attrib->Print( streamer );
  461. }
  462. for( XMLNode* node=firstChild; node; node=node->next ) {
  463. node->Print( streamer );
  464. }
  465. streamer->CloseElement();
  466. }
  467. // --------- XMLDocument ----------- //
  468. XMLDocument::XMLDocument() :
  469. XMLNode( 0 ),
  470. charBuffer( 0 )
  471. {
  472. document = this; // avoid warning about 'this' in initializer list
  473. }
  474. XMLDocument::~XMLDocument()
  475. {
  476. ClearChildren();
  477. delete [] charBuffer;
  478. /*
  479. textPool.Trace( "text" );
  480. elementPool.Trace( "element" );
  481. commentPool.Trace( "comment" );
  482. attributePool.Trace( "attribute" );
  483. */
  484. TIXMLASSERT( textPool.CurrentAllocs() == 0 );
  485. TIXMLASSERT( elementPool.CurrentAllocs() == 0 );
  486. TIXMLASSERT( commentPool.CurrentAllocs() == 0 );
  487. TIXMLASSERT( attributePool.CurrentAllocs() == 0 );
  488. }
  489. void XMLDocument::InitDocument()
  490. {
  491. errorID = NO_ERROR;
  492. errorStr1 = 0;
  493. errorStr2 = 0;
  494. delete [] charBuffer;
  495. charBuffer = 0;
  496. }
  497. XMLElement* XMLDocument::NewElement( const char* name )
  498. {
  499. XMLElement* ele = new (elementPool.Alloc()) XMLElement( this );
  500. ele->memPool = &elementPool;
  501. ele->SetName( name );
  502. return ele;
  503. }
  504. int XMLDocument::Parse( const char* p )
  505. {
  506. ClearChildren();
  507. InitDocument();
  508. if ( !p || !*p ) {
  509. return true; // correctly parse an empty string?
  510. }
  511. size_t len = strlen( p );
  512. charBuffer = new char[ len+1 ];
  513. memcpy( charBuffer, p, len+1 );
  514. XMLNode* node = 0;
  515. char* q = ParseDeep( charBuffer );
  516. return errorID;
  517. }
  518. void XMLDocument::Print( XMLStreamer* streamer )
  519. {
  520. XMLStreamer stdStreamer( stdout );
  521. if ( !streamer )
  522. streamer = &stdStreamer;
  523. for( XMLNode* node = firstChild; node; node=node->next ) {
  524. node->Print( streamer );
  525. }
  526. }
  527. void XMLDocument::SetError( int error, const char* str1, const char* str2 )
  528. {
  529. errorID = error;
  530. printf( "ERROR: id=%d '%s' '%s'\n", error, str1, str2 ); // fixme: remove
  531. errorStr1 = str1;
  532. errorStr2 = str2;
  533. }
  534. /*
  535. StringStack::StringStack()
  536. {
  537. nPositive = 0;
  538. mem.Push( 0 ); // start with null. makes later code simpler.
  539. }
  540. StringStack::~StringStack()
  541. {
  542. }
  543. void StringStack::Push( const char* str ) {
  544. int needed = strlen( str ) + 1;
  545. char* p = mem.PushArr( needed );
  546. strcpy( p, str );
  547. if ( needed > 1 )
  548. nPositive++;
  549. }
  550. const char* StringStack::Pop() {
  551. TIXMLASSERT( mem.Size() > 1 );
  552. const char* p = mem.Mem() + mem.Size() - 2; // end of final string.
  553. if ( *p ) {
  554. nPositive--;
  555. }
  556. while( *p ) { // stack starts with a null, don't need to check for 'mem'
  557. TIXMLASSERT( p > mem.Mem() );
  558. --p;
  559. }
  560. mem.PopArr( strlen(p)+1 );
  561. return p+1;
  562. }
  563. */
  564. XMLStreamer::XMLStreamer( FILE* file ) : fp( file ), depth( 0 ), elementJustOpened( false )
  565. {
  566. for( int i=0; i<ENTITY_RANGE; ++i ) {
  567. entityFlag[i] = false;
  568. }
  569. for( int i=0; i<NUM_ENTITIES; ++i ) {
  570. TIXMLASSERT( entities[i].value < ENTITY_RANGE );
  571. if ( entities[i].value < ENTITY_RANGE ) {
  572. entityFlag[ entities[i].value ] = true;
  573. }
  574. }
  575. }
  576. void XMLStreamer::PrintSpace( int depth )
  577. {
  578. for( int i=0; i<depth; ++i ) {
  579. fprintf( fp, " " );
  580. }
  581. }
  582. void XMLStreamer::PrintString( const char* p )
  583. {
  584. // Look for runs of bytes between entities to print.
  585. const char* q = p;
  586. while ( *q ) {
  587. if ( *q < ENTITY_RANGE ) {
  588. // Check for entities. If one is found, flush
  589. // the stream up until the entity, write the
  590. // entity, and keep looking.
  591. if ( entityFlag[*q] ) {
  592. while ( p < q ) {
  593. fputc( *p, fp );
  594. ++p;
  595. }
  596. for( int i=0; i<NUM_ENTITIES; ++i ) {
  597. if ( entities[i].value == *q ) {
  598. fprintf( fp, "&%s;", entities[i].pattern );
  599. break;
  600. }
  601. }
  602. ++p;
  603. }
  604. }
  605. ++q;
  606. }
  607. // Flush the remaining string. This will be the entire
  608. // string if an entity wasn't found.
  609. if ( q-p > 0 ) {
  610. fprintf( fp, "%s", p );
  611. }
  612. }
  613. void XMLStreamer::OpenElement( const char* name, bool textParent )
  614. {
  615. if ( elementJustOpened ) {
  616. SealElement();
  617. }
  618. if ( !TextOnStack() ) {
  619. PrintSpace( depth );
  620. }
  621. stack.Push( name );
  622. text.Push( textParent ? 'T' : 'e' );
  623. // fixme: can names have entities?
  624. fprintf( fp, "<%s", name );
  625. elementJustOpened = true;
  626. ++depth;
  627. }
  628. void XMLStreamer::PushAttribute( const char* name, const char* value )
  629. {
  630. TIXMLASSERT( elementJustOpened );
  631. fprintf( fp, " %s=\"", name );
  632. PrintString( value );
  633. fprintf( fp, "\"" );
  634. }
  635. void XMLStreamer::CloseElement()
  636. {
  637. --depth;
  638. const char* name = stack.Pop();
  639. bool wasText = TextOnStack();
  640. text.Pop();
  641. if ( elementJustOpened ) {
  642. fprintf( fp, "/>" );
  643. if ( !wasText ) {
  644. fprintf( fp, "\n" );
  645. }
  646. }
  647. else {
  648. if ( !wasText ) {
  649. PrintSpace( depth );
  650. }
  651. // fixme can names have entities?
  652. fprintf( fp, "</%s>", name );
  653. if ( !TextOnStack() ) {
  654. fprintf( fp, "\n" );
  655. }
  656. }
  657. elementJustOpened = false;
  658. }
  659. void XMLStreamer::SealElement()
  660. {
  661. elementJustOpened = false;
  662. fprintf( fp, ">" );
  663. if ( !TextOnStack() ) {
  664. fprintf( fp, "\n" );
  665. }
  666. }
  667. void XMLStreamer::PushText( const char* text )
  668. {
  669. if ( elementJustOpened ) {
  670. SealElement();
  671. }
  672. PrintString( text );
  673. }
  674. void XMLStreamer::PushComment( const char* comment )
  675. {
  676. if ( elementJustOpened ) {
  677. SealElement();
  678. }
  679. PrintSpace( depth );
  680. fprintf( fp, "<!--%s-->\n", comment );
  681. }