e3f42de68531ae59b6bee07b53f37992a03713c8
[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 = ".";
183 for ( i = 0; 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 ( const string& location_ )
343 : location(location_),
344 parentElement(NULL)
345 {
346 }
347
348 XMLElement::~XMLElement()
349 {
350 size_t i;
351 for ( i = 0; i < attributes.size(); i++ )
352 delete attributes[i];
353 for ( i = 0; i < subElements.size(); i++ )
354 delete subElements[i];
355 }
356
357 void
358 XMLElement::AddSubElement ( XMLElement* e )
359 {
360 subElements.push_back ( e );
361 e->parentElement = this;
362 }
363
364 // Parse()
365 // This function takes a single xml tag ( i.e. beginning with '<' and
366 // ending with '>', and parses out it's tag name and constituent
367 // attributes.
368 // Return Value: returns true if you need to look for a </tag> for
369 // the one it just parsed...
370 bool
371 XMLElement::Parse(const string& token,
372 bool& end_tag)
373 {
374 const char* p = token.c_str();
375 assert ( *p == '<' );
376 ++p;
377 p += strspn ( p, WS );
378
379 // check if this is a comment
380 if ( !strncmp ( p, "!--", 3 ) )
381 {
382 name = "!--";
383 end_tag = false;
384 return false; // never look for end tag to a comment
385 }
386
387 end_tag = ( *p == '/' );
388 if ( end_tag )
389 {
390 ++p;
391 p += strspn ( p, WS );
392 }
393 const char* end = strpbrk ( p, WS );
394 if ( !end )
395 {
396 end = strpbrk ( p, "/>" );
397 assert ( end );
398 }
399 name = string ( p, end-p );
400 p = end;
401 p += strspn ( p, WS );
402 while ( *p != '>' && *p != '/' )
403 {
404 end = strpbrk ( p, WSEQ );
405 if ( !end )
406 {
407 end = strpbrk ( p, "/>" );
408 assert ( end );
409 }
410 string attribute ( p, end-p ), value;
411 p = end;
412 p += strspn ( p, WS );
413 if ( *p == '=' )
414 {
415 ++p;
416 p += strspn ( p, WS );
417 char quote = 0;
418 if ( strchr ( "\"'", *p ) )
419 {
420 quote = *p++;
421 end = strchr ( p, quote );
422 }
423 else
424 {
425 end = strpbrk ( p, WS );
426 }
427 if ( !end )
428 {
429 end = strchr ( p, '>' );
430 assert(end);
431 if ( end[-1] == '/' )
432 end--;
433 }
434 value = string ( p, end-p );
435 p = end;
436 if ( quote && *p == quote )
437 p++;
438 p += strspn ( p, WS );
439 }
440 else if ( name[0] != '!' )
441 {
442 throw XMLSyntaxErrorException ( location,
443 "attributes must have values" );
444 }
445 attributes.push_back ( new XMLAttribute ( attribute, value ) );
446 }
447 return !( *p == '/' ) && !end_tag;
448 }
449
450 XMLAttribute*
451 XMLElement::GetAttribute ( const string& attribute,
452 bool required )
453 {
454 // this would be faster with a tree-based container, but our attribute
455 // lists are likely to stay so short as to not be an issue.
456 for ( size_t i = 0; i < attributes.size(); i++ )
457 {
458 if ( attribute == attributes[i]->name )
459 return attributes[i];
460 }
461 if ( required )
462 {
463 throw RequiredAttributeNotFoundException ( location,
464 attribute,
465 name );
466 }
467 return NULL;
468 }
469
470 const XMLAttribute*
471 XMLElement::GetAttribute ( const string& attribute,
472 bool required ) const
473 {
474 // this would be faster with a tree-based container, but our attribute
475 // lists are likely to stay so short as to not be an issue.
476 for ( size_t i = 0; i < attributes.size(); i++ )
477 {
478 if ( attribute == attributes[i]->name )
479 return attributes[i];
480 }
481 if ( required )
482 {
483 throw RequiredAttributeNotFoundException ( location,
484 attribute,
485 name );
486 }
487 return NULL;
488 }
489
490 // XMLParse()
491 // This function reads a "token" from the file loaded in XMLFile
492 // if it finds a tag that is non-singular, it parses sub-elements and/or
493 // inner text into the XMLElement that it is building to return.
494 // Return Value: an XMLElement allocated via the new operator that contains
495 // it's parsed data. Keep calling this function until it returns NULL
496 // (no more data)
497 XMLElement*
498 XMLParse ( XMLFile& f,
499 XMLIncludes* includes,
500 const Path& path,
501 bool* pend_tag = NULL )
502 {
503 string token;
504 if ( !f.get_token(token) )
505 return NULL;
506 bool end_tag, is_include = false;
507
508 while ( token[0] != '<'
509 || !strncmp ( token.c_str (), "<!--", 4 )
510 || !strncmp ( token.c_str (), "<?", 2 ) )
511 {
512 if ( token[0] != '<' )
513 throw XMLSyntaxErrorException ( f.Location (),
514 "expecting xml tag, not '%s'",
515 token.c_str () );
516 if ( !f.get_token(token) )
517 return NULL;
518 }
519
520 XMLElement* e = new XMLElement ( f.Location () );
521 bool bNeedEnd = e->Parse ( token, end_tag );
522
523 if ( e->name == "xi:include" && includes )
524 {
525 XMLAttribute* att;
526 att = e->GetAttribute ( "href", true );
527 assert ( att );
528 string includeFile ( path.Fixup ( att->value, true ) );
529 string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );
530 includes->push_back ( new XMLInclude ( e, path, topIncludeFile ) );
531 is_include = true;
532 }
533
534 if ( !bNeedEnd )
535 {
536 if ( pend_tag )
537 *pend_tag = end_tag;
538 else if ( end_tag )
539 {
540 delete e;
541 throw XMLSyntaxErrorException ( f.Location (),
542 "end tag '%s' not expected",
543 token.c_str() );
544 return NULL;
545 }
546 return e;
547 }
548 bool bThisMixingErrorReported = false;
549 while ( f.more_tokens () )
550 {
551 if ( f.next_is_text () )
552 {
553 if ( !f.get_token ( token ) || token.size () == 0 )
554 {
555 throw InvalidBuildFileException (
556 f.Location(),
557 "internal tool error - get_token() failed when more_tokens() returned true" );
558 break;
559 }
560 if ( e->subElements.size() && !bThisMixingErrorReported )
561 {
562 throw XMLSyntaxErrorException ( f.Location (),
563 "mixing of inner text with sub elements" );
564 bThisMixingErrorReported = true;
565 }
566 if ( strchr ( token.c_str (), '>' ) )
567 {
568 throw XMLSyntaxErrorException ( f.Location (),
569 "invalid symbol '>'" );
570 }
571 if ( e->value.size() > 0 )
572 {
573 throw XMLSyntaxErrorException ( f.Location (),
574 "multiple instances of inner text" );
575 e->value += " " + token;
576 }
577 else
578 e->value = token;
579 }
580 else
581 {
582 XMLElement* e2 = XMLParse ( f, is_include ? NULL : includes, path, &end_tag );
583 if ( !e2 )
584 {
585 throw InvalidBuildFileException (
586 e->location,
587 "end of file found looking for end tag" );
588 break;
589 }
590 if ( end_tag )
591 {
592 if ( e->name != e2->name )
593 {
594 delete e2;
595 throw XMLSyntaxErrorException ( f.Location (),
596 "end tag name mismatch" );
597 break;
598 }
599 delete e2;
600 break;
601 }
602 if ( e->value.size () > 0 && !bThisMixingErrorReported )
603 {
604 throw XMLSyntaxErrorException ( f.Location (),
605 "mixing of inner text with sub elements" );
606 bThisMixingErrorReported = true;
607 }
608 e->AddSubElement ( e2 );
609 }
610 }
611 return e;
612 }
613
614 void
615 XMLReadFile ( XMLFile& f, XMLElement& head, XMLIncludes& includes, const Path& path )
616 {
617 for ( ;; )
618 {
619 XMLElement* e = XMLParse ( f, &includes, path );
620 if ( !e )
621 return;
622 head.AddSubElement ( e );
623 }
624 }
625
626 XMLElement*
627 XMLLoadInclude ( XMLInclude& include,
628 XMLIncludes& includes )
629 {
630 XMLAttribute* att;
631 att = include.e->GetAttribute("href", true);
632 assert(att);
633
634 string file ( include.path.Fixup(att->value, true) );
635 string top_file ( Path::RelativeFromWorkingDirectory ( file ) );
636 include.e->attributes.push_back ( new XMLAttribute ( "top_href", top_file ) );
637 XMLFile fInc;
638 if ( !fInc.open ( file ) )
639 {
640 include.fileExists = false;
641 // look for xi:fallback element
642 for ( size_t i = 0; i < include.e->subElements.size (); i++ )
643 {
644 XMLElement* e2 = include.e->subElements[i];
645 if ( e2->name == "xi:fallback" )
646 {
647 // now look for xi:include below...
648 for ( i = 0; i < e2->subElements.size (); i++ )
649 {
650 XMLElement* e3 = e2->subElements[i];
651 if ( e3->name == "xi:include" )
652 {
653 att = e3->GetAttribute ( "href", true );
654 assert ( att );
655 string includeFile ( include.path.Fixup ( att->value, true ) );
656 string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );
657 XMLInclude* fallbackInclude = new XMLInclude ( e3, include.path, topIncludeFile );
658 return XMLLoadInclude ( *fallbackInclude, includes );
659 }
660 }
661 throw InvalidBuildFileException (
662 e2->location,
663 "<xi:fallback> must have a <xi:include> sub-element" );
664 return NULL;
665 }
666 }
667 return NULL;
668 }
669 else
670 {
671 include.fileExists = true;
672 XMLElement* new_e = new XMLElement ( include.e->location );
673 new_e->name = "xi:included";
674 Path path2 ( include.path, att->value );
675 XMLReadFile ( fInc, *new_e, includes, path2 );
676 return new_e;
677 }
678 }
679
680 XMLElement*
681 XMLLoadFile ( const string& filename,
682 const Path& path,
683 XMLIncludes& includes )
684 {
685 XMLFile f;
686
687 if ( !f.open ( filename ) )
688 throw FileNotFoundException ( filename );
689
690 XMLElement* head = new XMLElement ( "(virtual)" );
691
692 XMLReadFile ( f, *head, includes, path );
693
694 for ( size_t i = 0; i < includes.size (); i++ )
695 {
696 XMLElement* e = includes[i]->e;
697 XMLElement* e2 = XMLLoadInclude ( *includes[i], includes );
698 if ( !e2 )
699 {
700 throw FileNotFoundException (
701 ssprintf ( "%s (referenced from %s)",
702 e->GetAttribute ( "top_href", true )->value.c_str (),
703 f.Location ().c_str () ) );
704 }
705 XMLElement* parent = e->parentElement;
706 XMLElement** parent_container = NULL;
707 if ( !parent )
708 {
709 delete e;
710 throw Exception ( "internal tool error: xi:include doesn't have a parent" );
711 return NULL;
712 }
713 for ( size_t j = 0; j < parent->subElements.size (); j++ )
714 {
715 if ( parent->subElements[j] == e )
716 {
717 parent_container = &parent->subElements[j];
718 break;
719 }
720 }
721 if ( !parent_container )
722 {
723 delete e;
724 throw Exception ( "internal tool error: couldn't find xi:include in parent's sub-elements" );
725 return NULL;
726 }
727 // replace inclusion tree with the imported tree
728 e2->parentElement = e->parentElement;
729 e2->name = e->name;
730 e2->attributes = e->attributes;
731 *parent_container = e2;
732 e->attributes.resize ( 0 );
733 delete e;
734 }
735 return head;
736 }