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