2 * Copyright (C) 2005 Casper S. Hornstrup
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.
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.
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.
21 #define MAX_PATH _MAX_PATH
28 # include <sys/stat.h>
29 # define MAX_PATH PATH_MAX
34 #include "exception.h"
41 #define getcwd _getcwd
44 static const char* WS
= " \t\r\n";
45 static const char* WSEQ
= " =\t\r\n";
47 string working_directory
;
49 XMLIncludes::~XMLIncludes()
51 for ( size_t i
= 0; i
< this->size(); i
++ )
56 InitWorkingDirectory()
58 // store the current directory for path calculations
59 working_directory
.resize ( _MAX_PATH
);
60 working_directory
[0] = 0;
61 getcwd ( &working_directory
[0], working_directory
.size() );
62 working_directory
.resize ( strlen ( working_directory
.c_str() ) );
73 return _filelengthi64 ( _fileno(f
) );
75 struct stat64 file_stat
;
76 if ( fstat64(fileno(f
), &file_stat
) != 0 )
78 return file_stat
.st_size
;
84 if ( !working_directory
.size() )
85 InitWorkingDirectory();
86 string
s ( working_directory
);
87 const char* p
= strtok ( &s
[0], "/\\" );
92 p
= strtok ( NULL
, "/\\" );
96 Path::Path ( const Path
& cwd
, const string
& file
)
98 string
s ( cwd
.Fixup ( file
, false ) );
99 const char* p
= strtok ( &s
[0], "/\\" );
103 path
.push_back ( p
);
104 p
= strtok ( NULL
, "/\\" );
109 Path::Fixup ( const string
& file
, bool include_filename
) const
111 if ( strchr ( "/\\", file
[0] )
113 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
120 vector
<string
> pathtmp ( path
);
122 const char* prev
= strtok ( &tmp
[0], "/\\" );
123 const char* p
= strtok ( NULL
, "/\\" );
126 if ( !strcmp ( prev
, "." ) )
128 else if ( !strcmp ( prev
, ".." ) )
130 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
132 if ( pathtmp
.size() > 1 )
134 if ( pathtmp
.size() )
136 pathtmp
.resize ( pathtmp
.size() - 1 );
139 pathtmp
.push_back ( prev
);
141 p
= strtok ( NULL
, "/\\" );
143 if ( include_filename
)
144 pathtmp
.push_back ( prev
);
146 // reuse tmp variable to return recombined path
148 for ( size_t i
= 0; i
< pathtmp
.size(); i
++ )
150 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
162 Path::RelativeFromWorkingDirectory ()
165 for ( size_t i
= 0; i
< path
.size(); i
++ )
167 out
+= "/" + path
[i
];
169 return RelativeFromWorkingDirectory ( out
);
173 Path::RelativeFromWorkingDirectory ( const string
& path
)
175 return Path::RelativeFromDirectory ( path
, working_directory
);
179 Path::RelativeFromDirectory (
181 const string
& base_directory
)
183 vector
<string
> vbase
, vpath
, vout
;
184 Path::Split ( vbase
, base_directory
, true );
185 Path::Split ( vpath
, path
, true );
187 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
188 // not possible to do relative across different drive letters
190 char path_driveletter
= (path
[1] == ':') ? toupper(path
[0]) : 0;
191 char base_driveletter
= (base_directory
[1] == ':') ? toupper(base_directory
[0]) : 0;
192 if ( path_driveletter
!= base_driveletter
)
197 while ( i
< vbase
.size() && i
< vpath
.size() && vbase
[i
] == vpath
[i
] )
200 // did we go through all of the path?
201 if ( vbase
.size() == vpath
.size() && i
== vpath
.size() )
204 if ( i
< vbase
.size() )
206 // path goes above our base directory, we will need some ..'s
207 for ( size_t j
= i
; j
< vbase
.size(); j
++ )
208 vout
.push_back ( ".." );
211 while ( i
< vpath
.size() )
212 vout
.push_back ( vpath
[i
++] );
214 // now merge vout into a string again
215 string out
= vout
[0];
216 for ( i
= 1; i
< vout
.size(); i
++ )
218 out
+= "/" + vout
[i
];
224 Path::Split ( vector
<string
>& out
,
229 const char* prev
= strtok ( &s
[0], "/\\" );
230 const char* p
= strtok ( NULL
, "/\\" );
234 if ( strcmp ( prev
, "." ) )
235 out
.push_back ( prev
);
237 p
= strtok ( NULL
, "/\\" );
239 if ( include_last
&& strcmp ( prev
, "." ) )
240 out
.push_back ( prev
);
241 // special-case where path only has "."
242 // don't move this check up higher as it might miss
243 // some funny paths...
244 if ( !out
.size() && !strcmp ( prev
, "." ) )
245 out
.push_back ( "." );
260 XMLFile::open(const string
& filename_
)
263 FILE* f
= fopen ( filename_
.c_str(), "rb" );
266 unsigned long len
= (unsigned long)filelen(f
);
268 fread ( &_buf
[0], 1, len
, f
);
272 _filename
= filename_
;
277 // next_token() moves the pointer to next token, which may be
278 // an xml element or a text element, basically it's a glorified
279 // skipspace, normally the user of this class won't need to call
282 XMLFile::next_token()
284 _p
+= strspn ( _p
, WS
);
288 XMLFile::next_is_text()
294 XMLFile::more_tokens()
299 // get_token() is used to return a token, and move the pointer
302 XMLFile::get_token(string
& token
)
305 if ( !strncmp ( _p
, "<!--", 4 ) )
307 tokend
= strstr ( _p
, "-->" );
313 else if ( !strncmp ( _p
, "<?", 2 ) )
315 tokend
= strstr ( _p
, "?>" );
321 else if ( *_p
== '<' )
323 tokend
= strchr ( _p
, '>' );
331 tokend
= strchr ( _p
, '<' );
334 while ( tokend
> _p
&& isspace(tokend
[-1]) )
339 token
= string ( _p
, tokend
-_p
);
346 XMLFile::Location() const
349 const char* p
= strchr ( _buf
.c_str(), '\n' );
350 while ( p
&& p
< _p
)
353 p
= strchr ( p
+1, '\n' );
355 return ssprintf ( "%s(%i)",_filename
.c_str(), line
);
358 XMLAttribute::XMLAttribute()
362 XMLAttribute::XMLAttribute(const string
& name_
,
363 const string
& value_
)
364 : name(name_
), value(value_
)
368 XMLAttribute::XMLAttribute ( const XMLAttribute
& src
)
369 : name(src
.name
), value(src
.value
)
374 XMLAttribute
& XMLAttribute::operator = ( const XMLAttribute
& src
)
381 XMLElement::XMLElement ( XMLFile
* xmlFile
,
382 const string
& location
)
383 : xmlFile ( xmlFile
),
384 location ( location
),
385 parentElement ( NULL
)
389 XMLElement::~XMLElement()
392 for ( i
= 0; i
< attributes
.size(); i
++ )
393 delete attributes
[i
];
394 for ( i
= 0; i
< subElements
.size(); i
++ )
395 delete subElements
[i
];
399 XMLElement::AddSubElement ( XMLElement
* e
)
401 subElements
.push_back ( e
);
402 e
->parentElement
= this;
406 // This function takes a single xml tag ( i.e. beginning with '<' and
407 // ending with '>', and parses out it's tag name and constituent
409 // Return Value: returns true if you need to look for a </tag> for
410 // the one it just parsed...
412 XMLElement::Parse(const string
& token
,
415 const char* p
= token
.c_str();
416 assert ( *p
== '<' );
418 p
+= strspn ( p
, WS
);
420 // check if this is a comment
421 if ( !strncmp ( p
, "!--", 3 ) )
425 return false; // never look for end tag to a comment
428 end_tag
= ( *p
== '/' );
432 p
+= strspn ( p
, WS
);
434 const char* end
= strpbrk ( p
, WS
);
437 end
= strpbrk ( p
, "/>" );
440 name
= string ( p
, end
-p
);
442 p
+= strspn ( p
, WS
);
443 while ( *p
!= '>' && *p
!= '/' )
445 end
= strpbrk ( p
, WSEQ
);
448 end
= strpbrk ( p
, "/>" );
451 string
attribute ( p
, end
-p
), value
;
453 p
+= strspn ( p
, WS
);
457 p
+= strspn ( p
, WS
);
459 if ( strchr ( "\"'", *p
) )
462 end
= strchr ( p
, quote
);
466 end
= strpbrk ( p
, WS
);
470 end
= strchr ( p
, '>' );
472 if ( end
[-1] == '/' )
475 value
= string ( p
, end
-p
);
477 if ( quote
&& *p
== quote
)
479 p
+= strspn ( p
, WS
);
481 else if ( name
[0] != '!' )
483 throw XMLSyntaxErrorException ( location
,
484 "attributes must have values" );
486 attributes
.push_back ( new XMLAttribute ( attribute
, value
) );
488 return !( *p
== '/' ) && !end_tag
;
492 XMLElement::GetAttribute ( const string
& attribute
,
495 // this would be faster with a tree-based container, but our attribute
496 // lists are likely to stay so short as to not be an issue.
497 for ( size_t i
= 0; i
< attributes
.size(); i
++ )
499 if ( attribute
== attributes
[i
]->name
)
500 return attributes
[i
];
504 throw RequiredAttributeNotFoundException ( location
,
512 XMLElement::GetAttribute ( const string
& attribute
,
513 bool required
) const
515 // this would be faster with a tree-based container, but our attribute
516 // lists are likely to stay so short as to not be an issue.
517 for ( size_t i
= 0; i
< attributes
.size(); i
++ )
519 if ( attribute
== attributes
[i
]->name
)
520 return attributes
[i
];
524 throw RequiredAttributeNotFoundException ( location
,
532 // This function reads a "token" from the file loaded in XMLFile
533 // if it finds a tag that is non-singular, it parses sub-elements and/or
534 // inner text into the XMLElement that it is building to return.
535 // Return Value: an XMLElement allocated via the new operator that contains
536 // it's parsed data. Keep calling this function until it returns NULL
539 XMLParse ( XMLFile
& f
,
540 XMLIncludes
* includes
,
542 bool* pend_tag
= NULL
)
545 if ( !f
.get_token(token
) )
547 bool end_tag
, is_include
= false;
549 while ( token
[0] != '<'
550 || !strncmp ( token
.c_str (), "<!--", 4 )
551 || !strncmp ( token
.c_str (), "<?", 2 ) )
553 if ( token
[0] != '<' )
554 throw XMLSyntaxErrorException ( f
.Location (),
555 "expecting xml tag, not '%s'",
557 if ( !f
.get_token(token
) )
561 XMLElement
* e
= new XMLElement ( &f
,
563 bool bNeedEnd
= e
->Parse ( token
, end_tag
);
565 if ( e
->name
== "xi:include" && includes
)
568 att
= e
->GetAttribute ( "href", true );
570 string
includeFile ( path
.Fixup ( att
->value
, true ) );
571 string
topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile
) );
572 includes
->push_back ( new XMLInclude ( e
, path
, topIncludeFile
) );
583 throw XMLSyntaxErrorException ( f
.Location (),
584 "end tag '%s' not expected",
590 bool bThisMixingErrorReported
= false;
591 while ( f
.more_tokens () )
593 if ( f
.next_is_text () )
595 if ( !f
.get_token ( token
) || token
.size () == 0 )
597 throw InvalidBuildFileException (
599 "internal tool error - get_token() failed when more_tokens() returned true" );
602 if ( e
->subElements
.size() && !bThisMixingErrorReported
)
604 throw XMLSyntaxErrorException ( f
.Location (),
605 "mixing of inner text with sub elements" );
606 bThisMixingErrorReported
= true;
608 if ( strchr ( token
.c_str (), '>' ) )
610 throw XMLSyntaxErrorException ( f
.Location (),
611 "invalid symbol '>'" );
613 if ( e
->value
.size() > 0 )
615 throw XMLSyntaxErrorException ( f
.Location (),
616 "multiple instances of inner text" );
617 e
->value
+= " " + token
;
624 XMLElement
* e2
= XMLParse ( f
, is_include
? NULL
: includes
, path
, &end_tag
);
627 throw InvalidBuildFileException (
629 "end of file found looking for end tag" );
634 if ( e
->name
!= e2
->name
)
637 throw XMLSyntaxErrorException ( f
.Location (),
638 "end tag name mismatch" );
644 if ( e
->value
.size () > 0 && !bThisMixingErrorReported
)
646 throw XMLSyntaxErrorException ( f
.Location (),
647 "mixing of inner text with sub elements" );
648 bThisMixingErrorReported
= true;
650 e
->AddSubElement ( e2
);
657 XMLReadFile ( XMLFile
& f
, XMLElement
& head
, XMLIncludes
& includes
, const Path
& path
)
661 XMLElement
* e
= XMLParse ( f
, &includes
, path
);
664 head
.AddSubElement ( e
);
669 XMLLoadInclude ( XMLInclude
& include
,
670 XMLIncludes
& includes
)
673 att
= include
.e
->GetAttribute("href", true);
676 string
file ( include
.path
.Fixup(att
->value
, true) );
677 string
top_file ( Path::RelativeFromWorkingDirectory ( file
) );
678 include
.e
->attributes
.push_back ( new XMLAttribute ( "top_href", top_file
) );
679 XMLFile
* fInc
= new XMLFile();
680 if ( !fInc
->open ( file
) )
682 include
.fileExists
= false;
683 // look for xi:fallback element
684 for ( size_t i
= 0; i
< include
.e
->subElements
.size (); i
++ )
686 XMLElement
* e2
= include
.e
->subElements
[i
];
687 if ( e2
->name
== "xi:fallback" )
689 // now look for xi:include below...
690 for ( i
= 0; i
< e2
->subElements
.size (); i
++ )
692 XMLElement
* e3
= e2
->subElements
[i
];
693 if ( e3
->name
== "xi:include" )
695 att
= e3
->GetAttribute ( "href", true );
697 string
includeFile ( include
.path
.Fixup ( att
->value
, true ) );
698 string
topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile
) );
699 XMLInclude
* fallbackInclude
= new XMLInclude ( e3
, include
.path
, topIncludeFile
);
700 return XMLLoadInclude ( *fallbackInclude
, includes
);
703 throw InvalidBuildFileException (
705 "<xi:fallback> must have a <xi:include> sub-element" );
713 include
.fileExists
= true;
714 XMLElement
* new_e
= new XMLElement ( fInc
,
715 include
.e
->location
);
716 new_e
->name
= "xi:included";
717 Path
path2 ( include
.path
, att
->value
);
718 XMLReadFile ( *fInc
, *new_e
, includes
, path2
);
724 XMLLoadFile ( const string
& filename
,
726 XMLIncludes
& includes
)
728 XMLFile
* f
= new XMLFile();
730 if ( !f
->open ( filename
) )
731 throw FileNotFoundException ( filename
);
733 XMLElement
* head
= new XMLElement ( f
,
736 XMLReadFile ( *f
, *head
, includes
, path
);
738 for ( size_t i
= 0; i
< includes
.size (); i
++ )
740 XMLElement
* e
= includes
[i
]->e
;
741 XMLElement
* e2
= XMLLoadInclude ( *includes
[i
], includes
);
744 throw FileNotFoundException (
745 ssprintf ( "%s (referenced from %s)",
746 e
->GetAttribute ( "top_href", true )->value
.c_str (),
747 f
->Location ().c_str () ) );
749 XMLElement
* parent
= e
->parentElement
;
750 XMLElement
** parent_container
= NULL
;
754 throw Exception ( "internal tool error: xi:include doesn't have a parent" );
757 for ( size_t j
= 0; j
< parent
->subElements
.size (); j
++ )
759 if ( parent
->subElements
[j
] == e
)
761 parent_container
= &parent
->subElements
[j
];
765 if ( !parent_container
)
768 throw Exception ( "internal tool error: couldn't find xi:include in parent's sub-elements" );
771 // replace inclusion tree with the imported tree
772 e2
->parentElement
= e
->parentElement
;
774 e2
->attributes
= e
->attributes
;
775 *parent_container
= e2
;
776 e
->attributes
.resize ( 0 );