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