Have the object files of a module depend on the build file that contain the module
[reactos.git] / reactos / tools / rbuild / XML.cpp
1 // XML.cpp
2
3 #include "pch.h"
4
5 #ifdef _MSC_VER
6 #define MAX_PATH _MAX_PATH
7 #endif
8
9 #ifdef WIN32
10 # include <direct.h>
11 # include <io.h>
12 #else
13 # include <sys/stat.h>
14 # define MAX_PATH PATH_MAX
15 #endif
16 #include <assert.h>
17
18 #include "XML.h"
19 #include "exception.h"
20 #include "ssprintf.h"
21
22 using std::string;
23 using std::vector;
24
25 #ifdef WIN32
26 #define getcwd _getcwd
27 #endif//WIN32
28
29 static const char* WS = " \t\r\n";
30 static const char* WSEQ = " =\t\r\n";
31
32 string working_directory;
33
34 XMLIncludes::~XMLIncludes()
35 {
36 for ( size_t i = 0; i < this->size(); i++ )
37 delete (*this)[i];
38 }
39
40 void
41 InitWorkingDirectory()
42 {
43 // store the current directory for path calculations
44 working_directory.resize ( _MAX_PATH );
45 working_directory[0] = 0;
46 getcwd ( &working_directory[0], working_directory.size() );
47 working_directory.resize ( strlen ( working_directory.c_str() ) );
48 }
49
50 #ifdef _MSC_VER
51 unsigned __int64
52 #else
53 unsigned long long
54 #endif
55 filelen ( FILE* f )
56 {
57 #ifdef WIN32
58 return _filelengthi64 ( _fileno(f) );
59 #else
60 struct stat64 file_stat;
61 if ( fstat64(fileno(f), &file_stat) != 0 )
62 return 0;
63 return file_stat.st_size;
64 #endif
65 }
66
67 Path::Path()
68 {
69 if ( !working_directory.size() )
70 InitWorkingDirectory();
71 string s ( working_directory );
72 const char* p = strtok ( &s[0], "/\\" );
73 while ( p )
74 {
75 if ( *p )
76 path.push_back ( p );
77 p = strtok ( NULL, "/\\" );
78 }
79 }
80
81 Path::Path ( const Path& cwd, const string& file )
82 {
83 string s ( cwd.Fixup ( file, false ) );
84 const char* p = strtok ( &s[0], "/\\" );
85 while ( p )
86 {
87 if ( *p )
88 path.push_back ( p );
89 p = strtok ( NULL, "/\\" );
90 }
91 }
92
93 string
94 Path::Fixup ( const string& file, bool include_filename ) const
95 {
96 if ( strchr ( "/\\", file[0] )
97 #ifdef WIN32
98 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
99 || file[1] == ':'
100 #endif//WIN32
101 )
102 {
103 return file;
104 }
105 vector<string> pathtmp ( path );
106 string tmp ( file );
107 const char* prev = strtok ( &tmp[0], "/\\" );
108 const char* p = strtok ( NULL, "/\\" );
109 while ( p )
110 {
111 if ( !strcmp ( prev, "." ) )
112 ; // do nothing
113 else if ( !strcmp ( prev, ".." ) )
114 {
115 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
116 #ifdef WIN32
117 if ( pathtmp.size() > 1 )
118 #else
119 if ( pathtmp.size() )
120 #endif
121 pathtmp.resize ( pathtmp.size() - 1 );
122 }
123 else
124 pathtmp.push_back ( prev );
125 prev = p;
126 p = strtok ( NULL, "/\\" );
127 }
128 if ( include_filename )
129 pathtmp.push_back ( prev );
130
131 // reuse tmp variable to return recombined path
132 tmp.resize(0);
133 for ( size_t i = 0; i < pathtmp.size(); i++ )
134 {
135 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
136 #ifdef WIN32
137 if ( i ) tmp += "/";
138 #else
139 tmp += "/";
140 #endif
141 tmp += pathtmp[i];
142 }
143 return tmp;
144 }
145
146 string
147 Path::RelativeFromWorkingDirectory ()
148 {
149 string out = "";
150 for ( size_t i = 0; i < path.size(); i++ )
151 {
152 out += "/" + path[i];
153 }
154 return RelativeFromWorkingDirectory ( out );
155 }
156
157 string
158 Path::RelativeFromWorkingDirectory ( const string& path )
159 {
160 vector<string> vwork, vpath, vout;
161 Path::Split ( vwork, working_directory, true );
162 Path::Split ( vpath, path, true );
163 #ifdef WIN32
164 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
165 // not possible to do relative across different drive letters
166 if ( vwork[0] != vpath[0] )
167 return path;
168 #endif
169 size_t i = 0;
170 while ( i < vwork.size() && i < vpath.size() && vwork[i] == vpath[i] )
171 ++i;
172 if ( i < vwork.size() )
173 {
174 // path goes above our working directory, we will need some ..'s
175 for ( size_t j = 0; j < i; j++ )
176 vout.push_back ( ".." );
177 }
178 while ( i < vpath.size() )
179 vout.push_back ( vpath[i++] );
180
181 // now merge vout into a string again
182 string out = vout[0];
183 for ( i = 1; i < vout.size(); i++ )
184 {
185 out += "/" + vout[i];
186 }
187 return out;
188 }
189
190 void
191 Path::Split ( vector<string>& out,
192 const string& path,
193 bool include_last )
194 {
195 string s ( path );
196 const char* prev = strtok ( &s[0], "/\\" );
197 const char* p = strtok ( NULL, "/\\" );
198 out.resize ( 0 );
199 while ( p )
200 {
201 out.push_back ( prev );
202 prev = p;
203 p = strtok ( NULL, "/\\" );
204 }
205 if ( include_last )
206 out.push_back ( prev );
207 }
208
209 XMLFile::XMLFile()
210 {
211 }
212
213 void
214 XMLFile::close()
215 {
216 _buf.resize(0);
217 _p = _end = NULL;
218 }
219
220 bool
221 XMLFile::open(const string& filename_)
222 {
223 close();
224 FILE* f = fopen ( filename_.c_str(), "rb" );
225 if ( !f )
226 return false;
227 unsigned long len = (unsigned long)filelen(f);
228 _buf.resize ( len );
229 fread ( &_buf[0], 1, len, f );
230 fclose ( f );
231 _p = _buf.c_str();
232 _end = _p + len;
233 _filename = filename_;
234 next_token();
235 return true;
236 }
237
238 // next_token() moves the pointer to next token, which may be
239 // an xml element or a text element, basically it's a glorified
240 // skipspace, normally the user of this class won't need to call
241 // this function
242 void
243 XMLFile::next_token()
244 {
245 _p += strspn ( _p, WS );
246 }
247
248 bool
249 XMLFile::next_is_text()
250 {
251 return *_p != '<';
252 }
253
254 bool
255 XMLFile::more_tokens()
256 {
257 return _p != _end;
258 }
259
260 // get_token() is used to return a token, and move the pointer
261 // past the token
262 bool
263 XMLFile::get_token(string& token)
264 {
265 const char* tokend;
266 if ( !strncmp ( _p, "<!--", 4 ) )
267 {
268 tokend = strstr ( _p, "-->" );
269 if ( !tokend )
270 tokend = _end;
271 else
272 tokend += 3;
273 }
274 else if ( !strncmp ( _p, "<?", 2 ) )
275 {
276 tokend = strstr ( _p, "?>" );
277 if ( !tokend )
278 tokend = _end;
279 else
280 tokend += 2;
281 }
282 else if ( *_p == '<' )
283 {
284 tokend = strchr ( _p, '>' );
285 if ( !tokend )
286 tokend = _end;
287 else
288 ++tokend;
289 }
290 else
291 {
292 tokend = strchr ( _p, '<' );
293 if ( !tokend )
294 tokend = _end;
295 while ( tokend > _p && isspace(tokend[-1]) )
296 --tokend;
297 }
298 if ( tokend == _p )
299 return false;
300 token = string ( _p, tokend-_p );
301 _p = tokend;
302 next_token();
303 return true;
304 }
305
306 string
307 XMLFile::Location() const
308 {
309 int line = 1;
310 const char* p = strchr ( _buf.c_str(), '\n' );
311 while ( p && p < _p )
312 {
313 ++line;
314 p = strchr ( p+1, '\n' );
315 }
316 return ssprintf ( "%s(%i)",_filename.c_str(), line );
317 }
318
319 XMLAttribute::XMLAttribute()
320 {
321 }
322
323 XMLAttribute::XMLAttribute(const string& name_,
324 const string& value_)
325 : name(name_), value(value_)
326 {
327 }
328
329 XMLAttribute::XMLAttribute ( const XMLAttribute& src )
330 : name(src.name), value(src.value)
331 {
332
333 }
334
335 XMLAttribute& XMLAttribute::operator = ( const XMLAttribute& src )
336 {
337 name = src.name;
338 value = src.value;
339 return *this;
340 }
341
342 XMLElement::XMLElement ( XMLFile* xmlFile,
343 const string& location )
344 : xmlFile ( xmlFile ),
345 location ( location ),
346 parentElement ( NULL )
347 {
348 }
349
350 XMLElement::~XMLElement()
351 {
352 size_t i;
353 for ( i = 0; i < attributes.size(); i++ )
354 delete attributes[i];
355 for ( i = 0; i < subElements.size(); i++ )
356 delete subElements[i];
357 }
358
359 void
360 XMLElement::AddSubElement ( XMLElement* e )
361 {
362 subElements.push_back ( e );
363 e->parentElement = this;
364 }
365
366 // Parse()
367 // This function takes a single xml tag ( i.e. beginning with '<' and
368 // ending with '>', and parses out it's tag name and constituent
369 // attributes.
370 // Return Value: returns true if you need to look for a </tag> for
371 // the one it just parsed...
372 bool
373 XMLElement::Parse(const string& token,
374 bool& end_tag)
375 {
376 const char* p = token.c_str();
377 assert ( *p == '<' );
378 ++p;
379 p += strspn ( p, WS );
380
381 // check if this is a comment
382 if ( !strncmp ( p, "!--", 3 ) )
383 {
384 name = "!--";
385 end_tag = false;
386 return false; // never look for end tag to a comment
387 }
388
389 end_tag = ( *p == '/' );
390 if ( end_tag )
391 {
392 ++p;
393 p += strspn ( p, WS );
394 }
395 const char* end = strpbrk ( p, WS );
396 if ( !end )
397 {
398 end = strpbrk ( p, "/>" );
399 assert ( end );
400 }
401 name = string ( p, end-p );
402 p = end;
403 p += strspn ( p, WS );
404 while ( *p != '>' && *p != '/' )
405 {
406 end = strpbrk ( p, WSEQ );
407 if ( !end )
408 {
409 end = strpbrk ( p, "/>" );
410 assert ( end );
411 }
412 string attribute ( p, end-p ), value;
413 p = end;
414 p += strspn ( p, WS );
415 if ( *p == '=' )
416 {
417 ++p;
418 p += strspn ( p, WS );
419 char quote = 0;
420 if ( strchr ( "\"'", *p ) )
421 {
422 quote = *p++;
423 end = strchr ( p, quote );
424 }
425 else
426 {
427 end = strpbrk ( p, WS );
428 }
429 if ( !end )
430 {
431 end = strchr ( p, '>' );
432 assert(end);
433 if ( end[-1] == '/' )
434 end--;
435 }
436 value = string ( p, end-p );
437 p = end;
438 if ( quote && *p == quote )
439 p++;
440 p += strspn ( p, WS );
441 }
442 else if ( name[0] != '!' )
443 {
444 throw XMLSyntaxErrorException ( location,
445 "attributes must have values" );
446 }
447 attributes.push_back ( new XMLAttribute ( attribute, value ) );
448 }
449 return !( *p == '/' ) && !end_tag;
450 }
451
452 XMLAttribute*
453 XMLElement::GetAttribute ( const string& attribute,
454 bool required )
455 {
456 // this would be faster with a tree-based container, but our attribute
457 // lists are likely to stay so short as to not be an issue.
458 for ( size_t i = 0; i < attributes.size(); i++ )
459 {
460 if ( attribute == attributes[i]->name )
461 return attributes[i];
462 }
463 if ( required )
464 {
465 throw RequiredAttributeNotFoundException ( location,
466 attribute,
467 name );
468 }
469 return NULL;
470 }
471
472 const XMLAttribute*
473 XMLElement::GetAttribute ( const string& attribute,
474 bool required ) const
475 {
476 // this would be faster with a tree-based container, but our attribute
477 // lists are likely to stay so short as to not be an issue.
478 for ( size_t i = 0; i < attributes.size(); i++ )
479 {
480 if ( attribute == attributes[i]->name )
481 return attributes[i];
482 }
483 if ( required )
484 {
485 throw RequiredAttributeNotFoundException ( location,
486 attribute,
487 name );
488 }
489 return NULL;
490 }
491
492 // XMLParse()
493 // This function reads a "token" from the file loaded in XMLFile
494 // if it finds a tag that is non-singular, it parses sub-elements and/or
495 // inner text into the XMLElement that it is building to return.
496 // Return Value: an XMLElement allocated via the new operator that contains
497 // it's parsed data. Keep calling this function until it returns NULL
498 // (no more data)
499 XMLElement*
500 XMLParse ( XMLFile& f,
501 XMLIncludes* includes,
502 const Path& path,
503 bool* pend_tag = NULL )
504 {
505 string token;
506 if ( !f.get_token(token) )
507 return NULL;
508 bool end_tag, is_include = false;
509
510 while ( token[0] != '<'
511 || !strncmp ( token.c_str (), "<!--", 4 )
512 || !strncmp ( token.c_str (), "<?", 2 ) )
513 {
514 if ( token[0] != '<' )
515 throw XMLSyntaxErrorException ( f.Location (),
516 "expecting xml tag, not '%s'",
517 token.c_str () );
518 if ( !f.get_token(token) )
519 return NULL;
520 }
521
522 XMLElement* e = new XMLElement ( &f,
523 f.Location () );
524 bool bNeedEnd = e->Parse ( token, end_tag );
525
526 if ( e->name == "xi:include" && includes )
527 {
528 XMLAttribute* att;
529 att = e->GetAttribute ( "href", true );
530 assert ( att );
531 string includeFile ( path.Fixup ( att->value, true ) );
532 string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );
533 includes->push_back ( new XMLInclude ( e, path, topIncludeFile ) );
534 is_include = true;
535 }
536
537 if ( !bNeedEnd )
538 {
539 if ( pend_tag )
540 *pend_tag = end_tag;
541 else if ( end_tag )
542 {
543 delete e;
544 throw XMLSyntaxErrorException ( f.Location (),
545 "end tag '%s' not expected",
546 token.c_str() );
547 return NULL;
548 }
549 return e;
550 }
551 bool bThisMixingErrorReported = false;
552 while ( f.more_tokens () )
553 {
554 if ( f.next_is_text () )
555 {
556 if ( !f.get_token ( token ) || token.size () == 0 )
557 {
558 throw InvalidBuildFileException (
559 f.Location(),
560 "internal tool error - get_token() failed when more_tokens() returned true" );
561 break;
562 }
563 if ( e->subElements.size() && !bThisMixingErrorReported )
564 {
565 throw XMLSyntaxErrorException ( f.Location (),
566 "mixing of inner text with sub elements" );
567 bThisMixingErrorReported = true;
568 }
569 if ( strchr ( token.c_str (), '>' ) )
570 {
571 throw XMLSyntaxErrorException ( f.Location (),
572 "invalid symbol '>'" );
573 }
574 if ( e->value.size() > 0 )
575 {
576 throw XMLSyntaxErrorException ( f.Location (),
577 "multiple instances of inner text" );
578 e->value += " " + token;
579 }
580 else
581 e->value = token;
582 }
583 else
584 {
585 XMLElement* e2 = XMLParse ( f, is_include ? NULL : includes, path, &end_tag );
586 if ( !e2 )
587 {
588 throw InvalidBuildFileException (
589 e->location,
590 "end of file found looking for end tag" );
591 break;
592 }
593 if ( end_tag )
594 {
595 if ( e->name != e2->name )
596 {
597 delete e2;
598 throw XMLSyntaxErrorException ( f.Location (),
599 "end tag name mismatch" );
600 break;
601 }
602 delete e2;
603 break;
604 }
605 if ( e->value.size () > 0 && !bThisMixingErrorReported )
606 {
607 throw XMLSyntaxErrorException ( f.Location (),
608 "mixing of inner text with sub elements" );
609 bThisMixingErrorReported = true;
610 }
611 e->AddSubElement ( e2 );
612 }
613 }
614 return e;
615 }
616
617 void
618 XMLReadFile ( XMLFile& f, XMLElement& head, XMLIncludes& includes, const Path& path )
619 {
620 for ( ;; )
621 {
622 XMLElement* e = XMLParse ( f, &includes, path );
623 if ( !e )
624 return;
625 head.AddSubElement ( e );
626 }
627 }
628
629 XMLElement*
630 XMLLoadInclude ( XMLInclude& include,
631 XMLIncludes& includes )
632 {
633 XMLAttribute* att;
634 att = include.e->GetAttribute("href", true);
635 assert(att);
636
637 string file ( include.path.Fixup(att->value, true) );
638 string top_file ( Path::RelativeFromWorkingDirectory ( file ) );
639 include.e->attributes.push_back ( new XMLAttribute ( "top_href", top_file ) );
640 XMLFile* fInc = new XMLFile();
641 if ( !fInc->open ( file ) )
642 {
643 include.fileExists = false;
644 // look for xi:fallback element
645 for ( size_t i = 0; i < include.e->subElements.size (); i++ )
646 {
647 XMLElement* e2 = include.e->subElements[i];
648 if ( e2->name == "xi:fallback" )
649 {
650 // now look for xi:include below...
651 for ( i = 0; i < e2->subElements.size (); i++ )
652 {
653 XMLElement* e3 = e2->subElements[i];
654 if ( e3->name == "xi:include" )
655 {
656 att = e3->GetAttribute ( "href", true );
657 assert ( att );
658 string includeFile ( include.path.Fixup ( att->value, true ) );
659 string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );
660 XMLInclude* fallbackInclude = new XMLInclude ( e3, include.path, topIncludeFile );
661 return XMLLoadInclude ( *fallbackInclude, includes );
662 }
663 }
664 throw InvalidBuildFileException (
665 e2->location,
666 "<xi:fallback> must have a <xi:include> sub-element" );
667 return NULL;
668 }
669 }
670 return NULL;
671 }
672 else
673 {
674 include.fileExists = true;
675 XMLElement* new_e = new XMLElement ( fInc,
676 include.e->location );
677 new_e->name = "xi:included";
678 Path path2 ( include.path, att->value );
679 XMLReadFile ( *fInc, *new_e, includes, path2 );
680 return new_e;
681 }
682 }
683
684 XMLElement*
685 XMLLoadFile ( const string& filename,
686 const Path& path,
687 XMLIncludes& includes )
688 {
689 XMLFile* f = new XMLFile();
690
691 if ( !f->open ( filename ) )
692 throw FileNotFoundException ( filename );
693
694 XMLElement* head = new XMLElement ( f,
695 "(virtual)" );
696
697 XMLReadFile ( *f, *head, includes, path );
698
699 for ( size_t i = 0; i < includes.size (); i++ )
700 {
701 XMLElement* e = includes[i]->e;
702 XMLElement* e2 = XMLLoadInclude ( *includes[i], includes );
703 if ( !e2 )
704 {
705 throw FileNotFoundException (
706 ssprintf ( "%s (referenced from %s)",
707 e->GetAttribute ( "top_href", true )->value.c_str (),
708 f->Location ().c_str () ) );
709 }
710 XMLElement* parent = e->parentElement;
711 XMLElement** parent_container = NULL;
712 if ( !parent )
713 {
714 delete e;
715 throw Exception ( "internal tool error: xi:include doesn't have a parent" );
716 return NULL;
717 }
718 for ( size_t j = 0; j < parent->subElements.size (); j++ )
719 {
720 if ( parent->subElements[j] == e )
721 {
722 parent_container = &parent->subElements[j];
723 break;
724 }
725 }
726 if ( !parent_container )
727 {
728 delete e;
729 throw Exception ( "internal tool error: couldn't find xi:include in parent's sub-elements" );
730 return NULL;
731 }
732 // replace inclusion tree with the imported tree
733 e2->parentElement = e->parentElement;
734 e2->name = e->name;
735 e2->attributes = e->attributes;
736 *parent_container = e2;
737 e->attributes.resize ( 0 );
738 delete e;
739 }
740 return head;
741 }