- Properly set STDCALL as default convention, previous patch incorrectly set it as...
[reactos.git] / reactos / tools / xml.cpp
1 /*
2 * Copyright (C) 2005 Casper S. Hornstrup
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #ifdef _MSC_VER
20 #pragma warning ( disable : 4786 )
21 #endif//_MSC_VER
22
23 #ifdef WIN32
24 # include <direct.h>
25 # include <io.h>
26 #else
27 # include <sys/stat.h>
28 # define MAX_PATH PATH_MAX
29 #endif
30 #include <assert.h>
31
32 #include "xml.h"
33 #include "ssprintf.h"
34
35 #ifndef MAX_PATH
36 #define MAX_PATH _MAX_PATH
37 #endif
38
39 using std::string;
40 using std::vector;
41
42 #ifdef WIN32
43 #define getcwd _getcwd
44 #endif//WIN32
45
46 static const char* WS = " \t\r\n";
47 static const char* WSEQ = " =\t\r\n";
48
49 string working_directory;
50
51 XMLException::XMLException (
52 const std::string& location,
53 const char* format, ... )
54 {
55 va_list args;
56 va_start ( args, format );
57 SetExceptionV ( location, format, args );
58 va_end ( args );
59 }
60
61 void XMLException::SetExceptionV ( const std::string& location, const char* format, va_list args )
62 {
63 _e = location + ": " + ssvprintf(format,args);
64 }
65
66 void XMLException::SetException ( const std::string& location, const char* format, ... )
67 {
68 va_list args;
69 va_start ( args, format );
70 SetExceptionV ( location, format, args );
71 va_end ( args );
72 }
73
74 XMLIncludes::~XMLIncludes()
75 {
76 for ( size_t i = 0; i < this->size(); i++ )
77 delete (*this)[i];
78 }
79
80 void
81 InitWorkingDirectory()
82 {
83 // store the current directory for path calculations
84 working_directory.resize ( MAX_PATH );
85 working_directory[0] = 0;
86 getcwd ( &working_directory[0], working_directory.size() );
87 working_directory.resize ( strlen ( working_directory.c_str() ) );
88 }
89
90 #ifdef _MSC_VER
91 unsigned __int64
92 #else
93 unsigned long long
94 #endif
95 filelen ( FILE* f )
96 {
97 #ifdef WIN32
98 return _filelengthi64 ( _fileno(f) );
99 #else
100 # ifdef __FreeBSD__
101 struct stat file_stat;
102 if ( fstat(fileno(f), &file_stat) != 0 )
103 # else
104 struct stat64 file_stat;
105 if ( fstat64(fileno(f), &file_stat) != 0 )
106 # endif // __FreeBSD__
107 return 0;
108 return file_stat.st_size;
109 #endif // WIN32
110 }
111
112 Path::Path()
113 {
114 if ( !working_directory.size() )
115 InitWorkingDirectory();
116 string s ( working_directory );
117 const char* p = strtok ( &s[0], "/\\" );
118 while ( p )
119 {
120 if ( *p )
121 path.push_back ( p );
122 p = strtok ( NULL, "/\\" );
123 }
124 }
125
126 Path::Path ( const Path& cwd, const string& file )
127 {
128 string s ( cwd.Fixup ( file, false ) );
129 const char* p = strtok ( &s[0], "/\\" );
130 while ( p )
131 {
132 if ( *p )
133 path.push_back ( p );
134 p = strtok ( NULL, "/\\" );
135 }
136 }
137
138 string
139 Path::Fixup ( const string& file, bool include_filename ) const
140 {
141 if ( strchr ( "/\\", file[0] )
142 #ifdef WIN32
143 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
144 || file[1] == ':'
145 #endif//WIN32
146 )
147 {
148 return file;
149 }
150 vector<string> pathtmp ( path );
151 string tmp ( file );
152 const char* prev = strtok ( &tmp[0], "/\\" );
153 const char* p = strtok ( NULL, "/\\" );
154 while ( p )
155 {
156 if ( !strcmp ( prev, "." ) )
157 ; // do nothing
158 else if ( !strcmp ( prev, ".." ) )
159 {
160 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
161 #ifdef WIN32
162 if ( pathtmp.size() > 1 )
163 #else
164 if ( pathtmp.size() )
165 #endif
166 pathtmp.resize ( pathtmp.size() - 1 );
167 }
168 else
169 pathtmp.push_back ( prev );
170 prev = p;
171 p = strtok ( NULL, "/\\" );
172 }
173 if ( include_filename )
174 pathtmp.push_back ( prev );
175
176 // reuse tmp variable to return recombined path
177 tmp.resize(0);
178 for ( size_t i = 0; i < pathtmp.size(); i++ )
179 {
180 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
181 #ifdef WIN32
182 if ( i ) tmp += "/";
183 #else
184 tmp += "/";
185 #endif
186 tmp += pathtmp[i];
187 }
188 return tmp;
189 }
190
191 string
192 Path::RelativeFromWorkingDirectory ()
193 {
194 string out = "";
195 for ( size_t i = 0; i < path.size(); i++ )
196 {
197 out += "/" + path[i];
198 }
199 return RelativeFromWorkingDirectory ( out );
200 }
201
202 string
203 Path::RelativeFromWorkingDirectory ( const string& path )
204 {
205 return Path::RelativeFromDirectory ( path, working_directory );
206 }
207
208 string
209 Path::RelativeFromDirectory (
210 const string& path,
211 const string& base_directory )
212 {
213 vector<string> vbase, vpath, vout;
214 Path::Split ( vbase, base_directory, true );
215 Path::Split ( vpath, path, true );
216 #ifdef WIN32
217 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
218 // not possible to do relative across different drive letters
219 {
220 char path_driveletter = (path[1] == ':') ? toupper(path[0]) : 0;
221 char base_driveletter = (base_directory[1] == ':') ? toupper(base_directory[0]) : 0;
222 if ( path_driveletter != base_driveletter )
223 return path;
224 }
225 #endif
226 size_t i = 0;
227 while ( i < vbase.size() && i < vpath.size() && vbase[i] == vpath[i] )
228 ++i;
229
230 // did we go through all of the path?
231 if ( vbase.size() == vpath.size() && i == vpath.size() )
232 return ".";
233
234 if ( i < vbase.size() )
235 {
236 // path goes above our base directory, we will need some ..'s
237 for ( size_t j = i; j < vbase.size(); j++ )
238 vout.push_back ( ".." );
239 }
240
241 while ( i < vpath.size() )
242 vout.push_back ( vpath[i++] );
243
244 // now merge vout into a string again
245 string out = vout[0];
246 for ( i = 1; i < vout.size(); i++ )
247 {
248 out += "/" + vout[i];
249 }
250 return out;
251 }
252
253 void
254 Path::Split (
255 vector<string>& out,
256 const string& path,
257 bool include_last )
258 {
259 string s ( path );
260 const char* prev = strtok ( &s[0], "/\\" );
261 const char* p = strtok ( NULL, "/\\" );
262 out.resize ( 0 );
263 while ( p )
264 {
265 if ( strcmp ( prev, "." ) )
266 out.push_back ( prev );
267 prev = p;
268 p = strtok ( NULL, "/\\" );
269 }
270 if ( include_last && strcmp ( prev, "." ) )
271 out.push_back ( prev );
272 // special-case where path only has "."
273 // don't move this check up higher as it might miss
274 // some funny paths...
275 if ( !out.size() && !strcmp ( prev, "." ) )
276 out.push_back ( "." );
277 }
278
279 XMLFile::XMLFile()
280 {
281 }
282
283 void
284 XMLFile::close()
285 {
286 _buf.resize(0);
287 _p = _end = NULL;
288 }
289
290 bool
291 XMLFile::open ( const string& filename_ )
292 {
293 close();
294 FILE* f = fopen ( filename_.c_str(), "rb" );
295 if ( !f )
296 return false;
297 unsigned long len = (unsigned long)filelen(f);
298 _buf.resize ( len );
299 fread ( &_buf[0], 1, len, f );
300 fclose ( f );
301 _p = _buf.c_str();
302 _end = _p + len;
303 _filename = filename_;
304 next_token();
305 return true;
306 }
307
308 // next_token() moves the pointer to next token, which may be
309 // an xml element or a text element, basically it's a glorified
310 // skipspace, normally the user of this class won't need to call
311 // this function
312 void
313 XMLFile::next_token()
314 {
315 _p += strspn ( _p, WS );
316 }
317
318 bool
319 XMLFile::next_is_text()
320 {
321 return *_p != '<';
322 }
323
324 bool
325 XMLFile::more_tokens ()
326 {
327 return _p != _end;
328 }
329
330 // get_token() is used to return a token, and move the pointer
331 // past the token
332 bool
333 XMLFile::get_token ( string& token )
334 {
335 const char* tokend;
336 if ( !strncmp ( _p, "<!--", 4 ) )
337 {
338 tokend = strstr ( _p, "-->" );
339 if ( !tokend )
340 tokend = _end;
341 else
342 tokend += 3;
343 }
344 else if ( !strncmp ( _p, "<?", 2 ) )
345 {
346 tokend = strstr ( _p, "?>" );
347 if ( !tokend )
348 tokend = _end;
349 else
350 tokend += 2;
351 }
352 else if ( *_p == '<' )
353 {
354 tokend = strchr ( _p, '>' );
355 if ( !tokend )
356 tokend = _end;
357 else
358 ++tokend;
359 }
360 else
361 {
362 tokend = strchr ( _p, '<' );
363 if ( !tokend )
364 tokend = _end;
365 while ( tokend > _p && isspace(tokend[-1]) )
366 --tokend;
367 }
368 if ( tokend == _p )
369 return false;
370 token = string ( _p, tokend-_p );
371 _p = tokend;
372 next_token();
373 return true;
374 }
375
376 bool
377 XMLFile::get_token ( string& token, string& location )
378 {
379 location = Location();
380 return get_token ( token );
381 }
382
383 string
384 XMLFile::Location() const
385 {
386 int line = 1;
387 const char* p = strchr ( _buf.c_str(), '\n' );
388 while ( p && p < _p )
389 {
390 ++line;
391 p = strchr ( p+1, '\n' );
392 }
393 return ssprintf ( "%s(%i)",_filename.c_str(), line );
394 }
395
396 XMLAttribute::XMLAttribute()
397 {
398 }
399
400 XMLAttribute::XMLAttribute(
401 const string& name_,
402 const string& value_ )
403 : name(name_), value(value_)
404 {
405 }
406
407 XMLAttribute::XMLAttribute ( const XMLAttribute& src )
408 : name(src.name), value(src.value)
409 {
410
411 }
412
413 XMLAttribute& XMLAttribute::operator = ( const XMLAttribute& src )
414 {
415 name = src.name;
416 value = src.value;
417 return *this;
418 }
419
420 XMLElement::XMLElement (
421 XMLFile* xmlFile,
422 const string& location )
423 : xmlFile ( xmlFile ),
424 location ( location ),
425 parentElement ( NULL )
426 {
427 }
428
429 XMLElement::~XMLElement()
430 {
431 size_t i;
432 for ( i = 0; i < attributes.size(); i++ )
433 delete attributes[i];
434 for ( i = 0; i < subElements.size(); i++ )
435 delete subElements[i];
436 }
437
438 void
439 XMLElement::AddSubElement ( XMLElement* e )
440 {
441 subElements.push_back ( e );
442 e->parentElement = this;
443 }
444
445 // Parse()
446 // This function takes a single xml tag ( i.e. beginning with '<' and
447 // ending with '>', and parses out it's tag name and constituent
448 // attributes.
449 // Return Value: returns true if you need to look for a </tag> for
450 // the one it just parsed...
451 bool
452 XMLElement::Parse (
453 const string& token,
454 bool& end_tag )
455 {
456 const char* p = token.c_str();
457 assert ( *p == '<' );
458 ++p;
459 p += strspn ( p, WS );
460
461 // check if this is a comment
462 if ( !strncmp ( p, "!--", 3 ) )
463 {
464 name = "!--";
465 end_tag = false;
466 return false; // never look for end tag to a comment
467 }
468
469 end_tag = ( *p == '/' );
470 if ( end_tag )
471 {
472 ++p;
473 p += strspn ( p, WS );
474 }
475 const char* end = strpbrk ( p, WS );
476 if ( !end )
477 {
478 end = strpbrk ( p, "/>" );
479 assert ( end );
480 }
481 name = string ( p, end-p );
482 p = end;
483 p += strspn ( p, WS );
484 while ( *p != '>' && *p != '/' )
485 {
486 end = strpbrk ( p, WSEQ );
487 if ( !end )
488 {
489 end = strpbrk ( p, "/>" );
490 assert ( end );
491 }
492 string attribute ( p, end-p ), value;
493 p = end;
494 p += strspn ( p, WS );
495 if ( *p == '=' )
496 {
497 ++p;
498 p += strspn ( p, WS );
499 char quote = 0;
500 if ( strchr ( "\"'", *p ) )
501 {
502 quote = *p++;
503 end = strchr ( p, quote );
504 }
505 else
506 {
507 end = strpbrk ( p, WS );
508 }
509 if ( !end )
510 {
511 end = strchr ( p, '>' );
512 assert(end);
513 if ( end[-1] == '/' )
514 end--;
515 }
516 value = string ( p, end-p );
517 p = end;
518 if ( quote && *p == quote )
519 p++;
520 p += strspn ( p, WS );
521 }
522 else if ( name[0] != '!' )
523 {
524 throw XMLSyntaxErrorException (
525 location,
526 "attributes must have values" );
527 }
528 attributes.push_back ( new XMLAttribute ( attribute, value ) );
529 }
530 return !( *p == '/' ) && !end_tag;
531 }
532
533 XMLAttribute*
534 XMLElement::GetAttribute (
535 const string& attribute,
536 bool required )
537 {
538 // this would be faster with a tree-based container, but our attribute
539 // lists are likely to stay so short as to not be an issue.
540 for ( size_t i = 0; i < attributes.size(); i++ )
541 {
542 if ( attribute == attributes[i]->name )
543 return attributes[i];
544 }
545 if ( required )
546 {
547 throw XMLRequiredAttributeNotFoundException (
548 location,
549 attribute,
550 name );
551 }
552 return NULL;
553 }
554
555 const XMLAttribute*
556 XMLElement::GetAttribute (
557 const string& attribute,
558 bool required ) const
559 {
560 // this would be faster with a tree-based container, but our attribute
561 // lists are likely to stay so short as to not be an issue.
562 for ( size_t i = 0; i < attributes.size(); i++ )
563 {
564 if ( attribute == attributes[i]->name )
565 return attributes[i];
566 }
567 if ( required )
568 {
569 throw XMLRequiredAttributeNotFoundException (
570 location,
571 attribute,
572 name );
573 }
574 return NULL;
575 }
576
577 int
578 XMLElement::FindElement ( const std::string& type, int prev ) const
579 {
580 int done = subElements.size();
581 while ( ++prev < done )
582 {
583 XMLElement* e = subElements[prev];
584 if ( e->name == type )
585 return prev;
586 }
587 return -1;
588 }
589
590 int
591 XMLElement::GetElements (
592 const std::string& type,
593 std::vector<XMLElement*>& v )
594 {
595 int find = FindElement ( type );
596 v.resize ( 0 );
597 while ( find != -1 )
598 {
599 v.push_back ( subElements[find] );
600 find = FindElement ( type, find );
601 }
602 return v.size();
603 }
604
605 int
606 XMLElement::GetElements (
607 const std::string& type,
608 std::vector<const XMLElement*>& v ) const
609 {
610 int find = FindElement ( type );
611 v.resize ( 0 );
612 while ( find != -1 )
613 {
614 v.push_back ( subElements[find] );
615 find = FindElement ( type, find );
616 }
617 return v.size();
618 }
619
620 // XMLParse()
621 // This function reads a "token" from the file loaded in XMLFile
622 // if it finds a tag that is non-singular, it parses sub-elements and/or
623 // inner text into the XMLElement that it is building to return.
624 // Return Value: an XMLElement allocated via the new operator that contains
625 // it's parsed data. Keep calling this function until it returns NULL
626 // (no more data)
627 XMLElement*
628 XMLParse (
629 XMLFile& f,
630 XMLIncludes* includes,
631 const Path& path,
632 bool* pend_tag = NULL )
633 {
634 string token, location;
635 if ( !f.get_token(token,location) )
636 return NULL;
637 bool end_tag, is_include = false;
638
639 while
640 (
641 token[0] != '<'
642 || !strncmp ( token.c_str (), "<!--", 4 )
643 || !strncmp ( token.c_str (), "<?", 2 )
644 )
645 {
646 if ( token[0] != '<' )
647 {
648 throw XMLSyntaxErrorException (
649 location,
650 "expecting xml tag, not '%s'",
651 token.c_str () );
652 }
653 if ( !f.get_token ( token, location ) )
654 return NULL;
655 }
656
657 XMLElement* e = new XMLElement (
658 &f,
659 location );
660 bool bNeedEnd = e->Parse ( token, end_tag );
661
662 if ( e->name == "xi:include" && includes )
663 {
664 XMLAttribute* att;
665 att = e->GetAttribute ( "href", true );
666 assert ( att );
667 string includeFile ( path.Fixup ( att->value, true ) );
668 string topIncludeFile (
669 Path::RelativeFromWorkingDirectory ( includeFile ) );
670 includes->push_back (
671 new XMLInclude ( e, path, topIncludeFile ) );
672 is_include = true;
673 }
674
675 if ( !bNeedEnd )
676 {
677 if ( pend_tag )
678 *pend_tag = end_tag;
679 else if ( end_tag )
680 {
681 delete e;
682 throw XMLSyntaxErrorException (
683 location,
684 "end tag '%s' not expected",
685 token.c_str() );
686 return NULL;
687 }
688 return e;
689 }
690 bool bThisMixingErrorReported = false;
691 while ( f.more_tokens () )
692 {
693 if ( f.next_is_text () )
694 {
695 if ( !f.get_token ( token, location ) || token.size () == 0 )
696 {
697 throw XMLInvalidBuildFileException (
698 location,
699 "internal tool error - get_token() failed when more_tokens() returned true" );
700 break;
701 }
702 if ( e->subElements.size() && !bThisMixingErrorReported )
703 {
704 throw XMLSyntaxErrorException (
705 location,
706 "mixing of inner text with sub elements" );
707 bThisMixingErrorReported = true;
708 }
709 if ( strchr ( token.c_str (), '>' ) )
710 {
711 throw XMLSyntaxErrorException (
712 location,
713 "invalid symbol '>'" );
714 }
715 if ( e->value.size() > 0 )
716 {
717 throw XMLSyntaxErrorException (
718 location,
719 "multiple instances of inner text" );
720 e->value += " " + token;
721 }
722 else
723 e->value = token;
724 }
725 else
726 {
727 XMLElement* e2 = XMLParse (
728 f, is_include ? NULL : includes, path, &end_tag );
729 if ( !e2 )
730 {
731 string e_location = e->location;
732 string e_name = e->name;
733 delete e;
734 throw XMLInvalidBuildFileException (
735 e_location,
736 "end of file found looking for end tag: </%s>",
737 e_name.c_str() );
738 break;
739 }
740 if ( end_tag )
741 {
742 if ( e->name != e2->name )
743 {
744 string e2_location = e2->location;
745 string e_name = e->name;
746 string e2_name = e2->name;
747 delete e;
748 delete e2;
749 throw XMLSyntaxErrorException (
750 e2_location,
751 "end tag name mismatch - found </%s> but was expecting </%s>",
752 e2_name.c_str(),
753 e_name.c_str() );
754 break;
755 }
756 delete e2;
757 break;
758 }
759 if ( e->value.size () > 0 && !bThisMixingErrorReported )
760 {
761 string e_location = e->location;
762 delete e;
763 throw XMLSyntaxErrorException (
764 e_location,
765 "mixing of inner text with sub elements" );
766 bThisMixingErrorReported = true;
767 }
768 e->AddSubElement ( e2 );
769 }
770 }
771 return e;
772 }
773
774 void
775 XMLReadFile (
776 XMLFile& f,
777 XMLElement& head,
778 XMLIncludes& includes,
779 const Path& path )
780 {
781 for ( ;; )
782 {
783 XMLElement* e = XMLParse ( f, &includes, path );
784 if ( !e )
785 return;
786 head.AddSubElement ( e );
787 }
788 }
789
790 XMLElement*
791 XMLLoadInclude (
792 XMLInclude& include,
793 XMLIncludes& includes )
794 {
795 XMLAttribute* att;
796 att = include.e->GetAttribute("href", true);
797 assert(att);
798
799 string file ( include.path.Fixup(att->value, true) );
800 string top_file ( Path::RelativeFromWorkingDirectory ( file ) );
801 include.e->attributes.push_back ( new XMLAttribute ( "top_href", top_file ) );
802 XMLFile* fInc = new XMLFile();
803 if ( !fInc->open ( file ) )
804 {
805 include.fileExists = false;
806 // look for xi:fallback element
807 for ( size_t i = 0; i < include.e->subElements.size (); i++ )
808 {
809 XMLElement* e2 = include.e->subElements[i];
810 if ( e2->name == "xi:fallback" )
811 {
812 // now look for xi:include below...
813 for ( i = 0; i < e2->subElements.size (); i++ )
814 {
815 XMLElement* e3 = e2->subElements[i];
816 if ( e3->name == "xi:include" )
817 {
818 att = e3->GetAttribute ( "href", true );
819 assert ( att );
820 string includeFile (
821 include.path.Fixup ( att->value, true ) );
822 string topIncludeFile (
823 Path::RelativeFromWorkingDirectory ( includeFile ) );
824 XMLInclude* fallbackInclude =
825 new XMLInclude ( e3, include.path, topIncludeFile );
826 return XMLLoadInclude (
827 *fallbackInclude, includes );
828 }
829 }
830 throw XMLInvalidBuildFileException (
831 e2->location,
832 "<xi:fallback> must have a <xi:include> sub-element" );
833 return NULL;
834 }
835 }
836 return NULL;
837 }
838 else
839 {
840 include.fileExists = true;
841 XMLElement* new_e = new XMLElement (
842 fInc,
843 include.e->location );
844 new_e->name = "xi:included";
845 Path path2 ( include.path, att->value );
846 XMLReadFile ( *fInc, *new_e, includes, path2 );
847 return new_e;
848 }
849 }
850
851 XMLElement*
852 XMLLoadFile (
853 const string& filename,
854 const Path& path,
855 XMLIncludes& includes )
856 {
857 XMLFile* f = new XMLFile();
858
859 if ( !f->open ( filename ) )
860 {
861 throw XMLFileNotFoundException ( "(virtual)", filename );
862 return NULL;
863 }
864
865 XMLElement* head = new XMLElement ( f, "(virtual)" );
866
867 XMLReadFile ( *f, *head, includes, path );
868
869 for ( size_t i = 0; i < includes.size (); i++ )
870 {
871 XMLElement* e = includes[i]->e;
872 XMLElement* e2 = XMLLoadInclude ( *includes[i], includes );
873 if ( !e2 )
874 {
875 throw XMLFileNotFoundException (
876 f->Location(),
877 e->GetAttribute ( "top_href", true )->value );
878 }
879 XMLElement* parent = e->parentElement;
880 XMLElement** parent_container = NULL;
881 if ( !parent )
882 {
883 string location = e->location;
884 delete e;
885 throw XMLException ( location, "internal tool error: xi:include doesn't have a parent" );
886 return NULL;
887 }
888 for ( size_t j = 0; j < parent->subElements.size (); j++ )
889 {
890 if ( parent->subElements[j] == e )
891 {
892 parent_container = &parent->subElements[j];
893 break;
894 }
895 }
896 if ( !parent_container )
897 {
898 string location = e->location;
899 delete e;
900 throw XMLException ( location, "internal tool error: couldn't find xi:include in parent's sub-elements" );
901 return NULL;
902 }
903 // replace inclusion tree with the imported tree
904 e2->parentElement = e->parentElement;
905 e2->name = e->name;
906 e2->attributes = e->attributes;
907 *parent_container = e2;
908 e->attributes.resize ( 0 );
909 delete e;
910 }
911 return head;
912 }
913
914 XMLElement*
915 XMLLoadFile ( const string& filename )
916 {
917 Path path;
918 XMLIncludes includes;
919 return XMLLoadFile ( filename, path, includes );
920 }