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 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.
20 #pragma warning ( disable : 4786 )
30 // Some hosts don't define PATH_MAX in unistd.h
31 #if !defined(PATH_MAX)
35 #define MAX_PATH PATH_MAX
47 #define MAX_PATH _MAX_PATH
54 #define getcwd _getcwd
57 static const char* WS
= " \t\r\n";
58 static const char* WSEQ
= " =\t\r\n";
60 string working_directory
;
62 std::vector
<char> vectorize(const std::string
&str
)
64 std::vector
<char> result( str
.size() + 1 );
65 strcpy( &result
[0], str
.c_str() );
69 void vectappend(std::vector
<char> &strvec
, const char *str
)
71 if (*str
) { strvec
[strlen(&strvec
[0])] = *str
; str
++; }
74 strvec
.push_back(*str
);
80 void vectappend(std::vector
<char> &strvec
, const std::string
&str
)
82 vectappend(strvec
, str
.c_str());
85 void vectappend(std::vector
<char> &strvec
, char ch
)
87 strvec
[strlen(&strvec
[0])] = ch
;
91 XMLException::XMLException (
92 const std::string
& location
,
93 const char* format
, ... )
96 va_start ( args
, format
);
97 SetExceptionV ( location
, format
, args
);
101 void XMLException::SetExceptionV ( const std::string
& location
, const char* format
, va_list args
)
103 _e
= location
+ ": " + ssvprintf(format
,args
);
106 void XMLException::SetException ( const std::string
& location
, const char* format
, ... )
109 va_start ( args
, format
);
110 SetExceptionV ( location
, format
, args
);
114 XMLIncludes::~XMLIncludes()
116 for ( size_t i
= 0; i
< this->size(); i
++ )
121 InitWorkingDirectory()
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() ) );
138 return _filelengthi64 ( _fileno(f
) );
140 # if defined(__FreeBSD__) || defined(__APPLE__) || defined(__CYGWIN__)
141 struct stat file_stat
;
142 if ( fstat(fileno(f
), &file_stat
) != 0 )
144 struct stat64 file_stat
;
145 if ( fstat64(fileno(f
), &file_stat
) != 0 )
146 # endif // __FreeBSD__
148 return file_stat
.st_size
;
154 if ( !working_directory
.size() )
155 InitWorkingDirectory();
156 string
s ( working_directory
);
157 const char* p
= strtok ( &s
[0], "/\\" );
161 path
.push_back ( p
);
162 p
= strtok ( NULL
, "/\\" );
166 Path::Path ( const Path
& cwd
, const string
& file
)
168 string
s ( cwd
.Fixup ( file
, false ) );
169 const char* p
= strtok ( &s
[0], "/\\" );
173 path
.push_back ( p
);
174 p
= strtok ( NULL
, "/\\" );
179 Path::Fixup ( const string
& file
, bool include_filename
) const
181 if ( strchr ( "/\\", file
[0] )
183 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
190 vector
<string
> pathtmp ( path
);
191 vector
<char> tmp
= vectorize( file
);
192 const char* prev
= strtok ( &tmp
[0], "/\\" );
193 const char* p
= strtok ( NULL
, "/\\" );
196 if ( !strcmp ( prev
, "." ) )
198 else if ( !strcmp ( prev
, ".." ) )
200 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
202 if ( pathtmp
.size() > 1 )
204 if ( pathtmp
.size() )
206 pathtmp
.resize ( pathtmp
.size() - 1 );
209 pathtmp
.push_back ( prev
);
211 p
= strtok ( NULL
, "/\\" );
213 if ( include_filename
)
214 pathtmp
.push_back ( prev
);
216 // reuse tmp variable to return recombined path
218 for ( size_t i
= 0; i
< pathtmp
.size(); i
++ )
220 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
222 if ( i
) vectappend(tmp
, "/");
224 vectappend(tmp
, "/");
226 vectappend(tmp
, pathtmp
[i
]);
232 Path::RelativeFromWorkingDirectory ()
235 for ( size_t i
= 0; i
< path
.size(); i
++ )
237 out
+= "/" + path
[i
];
239 return RelativeFromWorkingDirectory ( out
);
243 Path::RelativeFromWorkingDirectory ( const string
& path
)
245 return Path::RelativeFromDirectory ( path
, working_directory
);
249 Path::RelativeFromDirectory (
251 const string
& base_directory
)
253 vector
<string
> vbase
, vpath
, vout
;
254 Path::Split ( vbase
, base_directory
, true );
255 Path::Split ( vpath
, path
, true );
257 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
258 // not possible to do relative across different drive letters
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
)
267 while ( i
< vbase
.size() && i
< vpath
.size() && vbase
[i
] == vpath
[i
] )
270 // did we go through all of the path?
271 if ( vbase
.size() == vpath
.size() && i
== vpath
.size() )
274 if ( i
< vbase
.size() )
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 ( ".." );
281 while ( i
< vpath
.size() )
282 vout
.push_back ( vpath
[i
++] );
284 // now merge vout into a string again
285 string out
= vout
[0];
286 for ( i
= 1; i
< vout
.size(); i
++ )
288 out
+= "/" + vout
[i
];
300 const char* prev
= strtok ( &s
[0], "/\\" );
301 const char* p
= strtok ( NULL
, "/\\" );
305 if ( strcmp ( prev
, "." ) )
306 out
.push_back ( prev
);
308 p
= strtok ( NULL
, "/\\" );
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 ( "." );
331 XMLFile::open ( const string
& filename_
)
334 FILE* f
= fopen ( filename_
.c_str(), "rb" );
337 unsigned long len
= (unsigned long)filelen(f
);
339 fread ( &_buf
[0], 1, len
, f
);
343 _filename
= filename_
;
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
353 XMLFile::next_token()
355 _p
+= strspn ( _p
, WS
);
359 XMLFile::next_is_text()
365 XMLFile::more_tokens ()
370 // get_token() is used to return a token, and move the pointer
373 XMLFile::get_token ( string
& token
)
376 if ( !strncmp ( _p
, "<!--", 4 ) )
378 tokend
= strstr ( _p
, "-->" );
384 else if ( !strncmp ( _p
, "<?", 2 ) )
386 tokend
= strstr ( _p
, "?>" );
392 else if ( *_p
== '<' )
394 tokend
= strchr ( _p
, '>' );
402 tokend
= strchr ( _p
, '<' );
405 while ( tokend
> _p
&& isspace(tokend
[-1]) )
410 token
= string ( _p
, tokend
-_p
);
417 XMLFile::get_token ( string
& token
, string
& location
)
419 location
= Location();
420 return get_token ( token
);
424 XMLFile::Location() const
427 const char* p
= strchr ( _buf
.c_str(), '\n' );
428 while ( p
&& p
< _p
)
431 p
= strchr ( p
+1, '\n' );
433 return ssprintf ( "%s(%i)",_filename
.c_str(), line
);
436 XMLAttribute::XMLAttribute()
440 XMLAttribute::XMLAttribute(
442 const string
& value_
)
443 : name(name_
), value(value_
)
447 XMLAttribute::XMLAttribute ( const XMLAttribute
& src
)
448 : name(src
.name
), value(src
.value
)
453 XMLAttribute
& XMLAttribute::operator = ( const XMLAttribute
& src
)
460 XMLElement::XMLElement (
462 const string
& location
)
463 : xmlFile ( xmlFile
),
464 location ( location
),
465 parentElement ( NULL
)
469 XMLElement::~XMLElement()
472 for ( i
= 0; i
< attributes
.size(); i
++ )
473 delete attributes
[i
];
474 for ( i
= 0; i
< subElements
.size(); i
++ )
475 delete subElements
[i
];
479 XMLElement::AddSubElement ( XMLElement
* e
)
481 subElements
.push_back ( e
);
482 e
->parentElement
= this;
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
489 // Return Value: returns true if you need to look for a </tag> for
490 // the one it just parsed...
496 const char* p
= token
.c_str();
497 assert ( *p
== '<' );
499 p
+= strspn ( p
, WS
);
501 // check if this is a comment
502 if ( !strncmp ( p
, "!--", 3 ) )
506 return false; // never look for end tag to a comment
509 end_tag
= ( *p
== '/' );
513 p
+= strspn ( p
, WS
);
515 const char* end
= strpbrk ( p
, WS
);
518 end
= strpbrk ( p
, "/>" );
521 name
= string ( p
, end
-p
);
523 p
+= strspn ( p
, WS
);
524 while ( *p
!= '>' && *p
!= '/' )
526 end
= strpbrk ( p
, WSEQ
);
529 end
= strpbrk ( p
, "/>" );
532 string
attribute ( p
, end
-p
), value
;
534 p
+= strspn ( p
, WS
);
538 p
+= strspn ( p
, WS
);
540 if ( strchr ( "\"'", *p
) )
543 end
= strchr ( p
, quote
);
547 end
= strpbrk ( p
, WS
);
551 end
= strchr ( p
, '>' );
553 if ( end
[-1] == '/' )
556 value
= string ( p
, end
-p
);
558 if ( quote
&& *p
== quote
)
560 p
+= strspn ( p
, WS
);
562 else if ( name
[0] != '!' )
564 throw XMLSyntaxErrorException (
566 "attributes must have values" );
568 attributes
.push_back ( new XMLAttribute ( attribute
, value
) );
570 return !( *p
== '/' ) && !end_tag
;
574 XMLElement::GetAttribute (
575 const string
& attribute
,
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
++ )
582 if ( attribute
== attributes
[i
]->name
)
583 return attributes
[i
];
587 throw XMLRequiredAttributeNotFoundException (
596 XMLElement::GetAttribute (
597 const string
& attribute
,
598 bool required
) const
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
++ )
604 if ( attribute
== attributes
[i
]->name
)
605 return attributes
[i
];
609 throw XMLRequiredAttributeNotFoundException (
618 XMLElement::FindElement ( const std::string
& type
, int prev
) const
620 int done
= subElements
.size();
621 while ( ++prev
< done
)
623 XMLElement
* e
= subElements
[prev
];
624 if ( e
->name
== type
)
631 XMLElement::GetElements (
632 const std::string
& type
,
633 std::vector
<XMLElement
*>& v
)
635 int find
= FindElement ( type
);
639 v
.push_back ( subElements
[find
] );
640 find
= FindElement ( type
, find
);
646 XMLElement::GetElements (
647 const std::string
& type
,
648 std::vector
<const XMLElement
*>& v
) const
650 int find
= FindElement ( type
);
654 v
.push_back ( subElements
[find
] );
655 find
= FindElement ( type
, find
);
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
670 XMLIncludes
* includes
,
672 bool* pend_tag
= NULL
)
674 string token
, location
;
675 if ( !f
.get_token(token
,location
) )
677 bool end_tag
, is_include
= false;
682 || !strncmp ( token
.c_str (), "<!--", 4 )
683 || !strncmp ( token
.c_str (), "<?", 2 )
686 if ( token
[0] != '<' )
688 throw XMLSyntaxErrorException (
690 "expecting xml tag, not '%s'",
693 if ( !f
.get_token ( token
, location
) )
697 XMLElement
* e
= new XMLElement (
700 bool bNeedEnd
= e
->Parse ( token
, end_tag
);
702 if ( e
->name
== "xi:include" && includes
)
705 att
= e
->GetAttribute ( "href", true );
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
) );
722 throw XMLSyntaxErrorException (
724 "end tag '%s' not expected",
730 bool bThisMixingErrorReported
= false;
731 while ( f
.more_tokens () )
733 if ( f
.next_is_text () )
735 if ( !f
.get_token ( token
, location
) || token
.size () == 0 )
737 throw XMLInvalidBuildFileException (
739 "internal tool error - get_token() failed when more_tokens() returned true" );
742 if ( e
->subElements
.size() && !bThisMixingErrorReported
)
744 throw XMLSyntaxErrorException (
746 "mixing of inner text with sub elements" );
747 bThisMixingErrorReported
= true;
749 if ( strchr ( token
.c_str (), '>' ) )
751 throw XMLSyntaxErrorException (
753 "invalid symbol '>'" );
755 if ( e
->value
.size() > 0 )
757 throw XMLSyntaxErrorException (
759 "multiple instances of inner text" );
760 e
->value
+= " " + token
;
767 XMLElement
* e2
= XMLParse (
768 f
, is_include
? NULL
: includes
, path
, &end_tag
);
771 string e_location
= e
->location
;
772 string e_name
= e
->name
;
774 throw XMLInvalidBuildFileException (
776 "end of file found looking for end tag: </%s>",
782 if ( e
->name
!= e2
->name
)
784 string e2_location
= e2
->location
;
785 string e_name
= e
->name
;
786 string e2_name
= e2
->name
;
789 throw XMLSyntaxErrorException (
791 "end tag name mismatch - found </%s> but was expecting </%s>",
799 if ( e
->value
.size () > 0 && !bThisMixingErrorReported
)
801 string e_location
= e
->location
;
803 throw XMLSyntaxErrorException (
805 "mixing of inner text with sub elements" );
806 bThisMixingErrorReported
= true;
808 e
->AddSubElement ( e2
);
818 XMLIncludes
& includes
,
823 XMLElement
* e
= XMLParse ( f
, &includes
, path
);
826 head
.AddSubElement ( e
);
833 XMLIncludes
& includes
)
836 att
= include
.e
->GetAttribute("href", true);
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
) )
845 include
.fileExists
= false;
846 // look for xi:fallback element
847 for ( size_t i
= 0; i
< include
.e
->subElements
.size (); i
++ )
849 XMLElement
* e2
= include
.e
->subElements
[i
];
850 if ( e2
->name
== "xi:fallback" )
852 // now look for xi:include below...
853 for ( i
= 0; i
< e2
->subElements
.size (); i
++ )
855 XMLElement
* e3
= e2
->subElements
[i
];
856 if ( e3
->name
== "xi:include" )
858 att
= e3
->GetAttribute ( "href", true );
861 include
.path
.Fixup ( att
->value
, true ) );
862 string
topIncludeFile (
863 Path::RelativeFromWorkingDirectory ( includeFile
) );
864 XMLInclude
* fallbackInclude
=
865 new XMLInclude ( e3
, include
.path
, topIncludeFile
);
867 XMLElement
* value
= XMLLoadInclude (*fallbackInclude
, includes
);
868 delete fallbackInclude
;
872 throw XMLInvalidBuildFileException (
874 "<xi:fallback> must have a <xi:include> sub-element" );
882 include
.fileExists
= true;
883 XMLElement
* new_e
= new XMLElement (
885 include
.e
->location
);
886 new_e
->name
= "xi:included";
887 Path
path2 ( include
.path
, att
->value
);
888 XMLReadFile ( *fInc
, *new_e
, includes
, path2
);
895 const string
& filename
,
897 XMLIncludes
& includes
)
899 XMLFile
* f
= new XMLFile();
901 if ( !f
->open ( filename
) )
904 throw XMLFileNotFoundException ( "(virtual)", filename
);
908 XMLElement
* head
= new XMLElement ( f
, "(virtual)" );
910 XMLReadFile ( *f
, *head
, includes
, path
);
912 for ( size_t i
= 0; i
< includes
.size (); i
++ )
914 XMLElement
* e
= includes
[i
]->e
;
915 XMLElement
* e2
= XMLLoadInclude ( *includes
[i
], includes
);
918 throw XMLFileNotFoundException (
920 e
->GetAttribute ( "top_href", true )->value
);
922 XMLElement
* parent
= e
->parentElement
;
923 XMLElement
** parent_container
= NULL
;
926 string location
= e
->location
;
929 throw XMLException ( location
, "internal tool error: xi:include doesn't have a parent" );
932 for ( size_t j
= 0; j
< parent
->subElements
.size (); j
++ )
934 if ( parent
->subElements
[j
] == e
)
936 parent_container
= &parent
->subElements
[j
];
940 if ( !parent_container
)
942 string location
= e
->location
;
945 throw XMLException ( location
, "internal tool error: couldn't find xi:include in parent's sub-elements" );
948 // replace inclusion tree with the imported tree
949 e2
->parentElement
= e
->parentElement
;
951 e2
->attributes
= e
->attributes
;
952 *parent_container
= e2
;
953 e
->attributes
.resize ( 0 );
961 XMLLoadFile ( const string
& filename
)
964 XMLIncludes includes
;
965 return XMLLoadFile ( filename
, path
, includes
);