-// XML.cpp\r
-\r
-#include "pch.h"\r
-\r
-#ifdef _MSC_VER\r
-#define MAX_PATH _MAX_PATH\r
-#endif\r
-\r
-#ifdef WIN32\r
-# include <direct.h>\r
-# include <io.h>\r
-#else\r
-# include <sys/stat.h>\r
-# define MAX_PATH PATH_MAX\r
-#endif\r
-#include <assert.h>\r
-\r
-#include "XML.h"\r
-#include "exception.h"\r
-#include "ssprintf.h"\r
-\r
-using std::string;\r
-using std::vector;\r
-\r
-#ifdef WIN32\r
-#define getcwd _getcwd\r
-#endif//WIN32\r
-\r
-static const char* WS = " \t\r\n";\r
-static const char* WSEQ = " =\t\r\n";\r
-\r
-string working_directory;\r
-\r
-XMLIncludes::~XMLIncludes()\r
-{\r
- for ( size_t i = 0; i < this->size(); i++ )\r
- delete (*this)[i];\r
-}\r
-\r
-void\r
-InitWorkingDirectory()\r
-{\r
- // store the current directory for path calculations\r
- working_directory.resize ( _MAX_PATH );\r
- working_directory[0] = 0;\r
- getcwd ( &working_directory[0], working_directory.size() );\r
- working_directory.resize ( strlen ( working_directory.c_str() ) );\r
-}\r
-\r
-#ifdef _MSC_VER\r
-unsigned __int64\r
-#else\r
-unsigned long long\r
-#endif\r
-filelen ( FILE* f )\r
-{\r
-#ifdef WIN32\r
- return _filelengthi64 ( _fileno(f) );\r
-#else\r
- struct stat64 file_stat;\r
- if ( fstat64(fileno(f), &file_stat) != 0 )\r
- return 0;\r
- return file_stat.st_size;\r
-#endif\r
-}\r
-\r
-Path::Path()\r
-{\r
- if ( !working_directory.size() )\r
- InitWorkingDirectory();\r
- string s ( working_directory );\r
- const char* p = strtok ( &s[0], "/\\" );\r
- while ( p )\r
- {\r
- if ( *p )\r
- path.push_back ( p );\r
- p = strtok ( NULL, "/\\" );\r
- }\r
-}\r
-\r
-Path::Path ( const Path& cwd, const string& file )\r
-{\r
- string s ( cwd.Fixup ( file, false ) );\r
- const char* p = strtok ( &s[0], "/\\" );\r
- while ( p )\r
- {\r
- if ( *p )\r
- path.push_back ( p );\r
- p = strtok ( NULL, "/\\" );\r
- }\r
-}\r
-\r
-string\r
-Path::Fixup ( const string& file, bool include_filename ) const\r
-{\r
- if ( strchr ( "/\\", file[0] )\r
-#ifdef WIN32\r
- // this squirreliness is b/c win32 has drive letters and *nix doesn't...\r
- || file[1] == ':'\r
-#endif//WIN32\r
- )\r
- {\r
- return file;\r
- }\r
- vector<string> pathtmp ( path );\r
- string tmp ( file );\r
- const char* prev = strtok ( &tmp[0], "/\\" );\r
- const char* p = strtok ( NULL, "/\\" );\r
- while ( p )\r
- {\r
- if ( !strcmp ( prev, "." ) )\r
- ; // do nothing\r
- else if ( !strcmp ( prev, ".." ) )\r
- {\r
- // this squirreliness is b/c win32 has drive letters and *nix doesn't...\r
-#ifdef WIN32\r
- if ( pathtmp.size() > 1 )\r
-#else\r
- if ( pathtmp.size() )\r
-#endif\r
- pathtmp.resize ( pathtmp.size() - 1 );\r
- }\r
- else\r
- pathtmp.push_back ( prev );\r
- prev = p;\r
- p = strtok ( NULL, "/\\" );\r
- }\r
- if ( include_filename )\r
- pathtmp.push_back ( prev );\r
-\r
- // reuse tmp variable to return recombined path\r
- tmp.resize(0);\r
- for ( size_t i = 0; i < pathtmp.size(); i++ )\r
- {\r
- // this squirreliness is b/c win32 has drive letters and *nix doesn't...\r
-#ifdef WIN32\r
- if ( i ) tmp += "/";\r
-#else\r
- tmp += "/";\r
-#endif\r
- tmp += pathtmp[i];\r
- }\r
- return tmp;\r
-}\r
-\r
-string\r
-Path::RelativeFromWorkingDirectory ()\r
-{\r
- string out = "";\r
- for ( size_t i = 0; i < path.size(); i++ )\r
- {\r
- out += "/" + path[i];\r
- }\r
- return RelativeFromWorkingDirectory ( out );\r
-}\r
-\r
-string\r
-Path::RelativeFromWorkingDirectory ( const string& path )\r
-{\r
- vector<string> vwork, vpath, vout;\r
- Path::Split ( vwork, working_directory, true );\r
- Path::Split ( vpath, path, true );\r
-#ifdef WIN32\r
- // this squirreliness is b/c win32 has drive letters and *nix doesn't...\r
- // not possible to do relative across different drive letters\r
- if ( vwork[0] != vpath[0] )\r
- return path;\r
-#endif\r
- size_t i = 0;\r
- while ( i < vwork.size() && i < vpath.size() && vwork[i] == vpath[i] )\r
- ++i;\r
- if ( i < vwork.size() )\r
- {\r
- // path goes above our working directory, we will need some ..'s\r
- for ( size_t j = 0; j < i; j++ )\r
- vout.push_back ( ".." );\r
- }\r
- while ( i < vpath.size() )\r
- vout.push_back ( vpath[i++] );\r
-\r
- // now merge vout into a string again\r
- string out = vout[0];\r
- for ( i = 1; i < vout.size(); i++ )\r
- {\r
- out += "/" + vout[i];\r
- }\r
- return out;\r
-}\r
-\r
-void\r
-Path::Split ( vector<string>& out,\r
- const string& path,\r
- bool include_last )\r
-{\r
- string s ( path );\r
- const char* prev = strtok ( &s[0], "/\\" );\r
- const char* p = strtok ( NULL, "/\\" );\r
- out.resize ( 0 );\r
- while ( p )\r
- {\r
- out.push_back ( prev );\r
- prev = p;\r
- p = strtok ( NULL, "/\\" );\r
- }\r
- if ( include_last )\r
- out.push_back ( prev );\r
-}\r
-\r
-XMLFile::XMLFile()\r
-{\r
-}\r
-\r
-void\r
-XMLFile::close()\r
-{\r
- _buf.resize(0);\r
- _p = _end = NULL;\r
-}\r
-\r
-bool\r
-XMLFile::open(const string& filename_)\r
-{\r
- close();\r
- FILE* f = fopen ( filename_.c_str(), "rb" );\r
- if ( !f )\r
- return false;\r
- unsigned long len = (unsigned long)filelen(f);\r
- _buf.resize ( len );\r
- fread ( &_buf[0], 1, len, f );\r
- fclose ( f );\r
- _p = _buf.c_str();\r
- _end = _p + len;\r
- _filename = filename_;\r
- next_token();\r
- return true;\r
-}\r
-\r
-// next_token() moves the pointer to next token, which may be\r
-// an xml element or a text element, basically it's a glorified\r
-// skipspace, normally the user of this class won't need to call\r
-// this function\r
-void\r
-XMLFile::next_token()\r
-{\r
- _p += strspn ( _p, WS );\r
-}\r
-\r
-bool\r
-XMLFile::next_is_text()\r
-{\r
- return *_p != '<';\r
-}\r
-\r
-bool\r
-XMLFile::more_tokens()\r
-{\r
- return _p != _end;\r
-}\r
-\r
-// get_token() is used to return a token, and move the pointer\r
-// past the token\r
-bool\r
-XMLFile::get_token(string& token)\r
-{\r
- const char* tokend;\r
- if ( !strncmp ( _p, "<!--", 4 ) )\r
- {\r
- tokend = strstr ( _p, "-->" );\r
- if ( !tokend )\r
- tokend = _end;\r
- else\r
- tokend += 3;\r
- }\r
- else if ( !strncmp ( _p, "<?", 2 ) )\r
- {\r
- tokend = strstr ( _p, "?>" );\r
- if ( !tokend )\r
- tokend = _end;\r
- else\r
- tokend += 2;\r
- }\r
- else if ( *_p == '<' )\r
- {\r
- tokend = strchr ( _p, '>' );\r
- if ( !tokend )\r
- tokend = _end;\r
- else\r
- ++tokend;\r
- }\r
- else\r
- {\r
- tokend = strchr ( _p, '<' );\r
- if ( !tokend )\r
- tokend = _end;\r
- while ( tokend > _p && isspace(tokend[-1]) )\r
- --tokend;\r
- }\r
- if ( tokend == _p )\r
- return false;\r
- token = string ( _p, tokend-_p );\r
- _p = tokend;\r
- next_token();\r
- return true;\r
-}\r
-\r
-string\r
-XMLFile::Location() const\r
-{\r
- int line = 1;\r
- const char* p = strchr ( _buf.c_str(), '\n' );\r
- while ( p && p < _p )\r
- {\r
- ++line;\r
- p = strchr ( p+1, '\n' );\r
- }\r
- return ssprintf ( "%s(%i)",_filename.c_str(), line );\r
-}\r
-\r
-XMLAttribute::XMLAttribute()\r
-{\r
-}\r
-\r
-XMLAttribute::XMLAttribute(const string& name_,\r
- const string& value_)\r
- : name(name_), value(value_)\r
-{\r
-}\r
-\r
-XMLAttribute::XMLAttribute ( const XMLAttribute& src )\r
- : name(src.name), value(src.value)\r
-{\r
-\r
-}\r
-\r
-XMLAttribute& XMLAttribute::operator = ( const XMLAttribute& src )\r
-{\r
- name = src.name;\r
- value = src.value;\r
- return *this;\r
-}\r
-\r
-XMLElement::XMLElement ( const string& location_ )\r
- : location(location_),\r
- parentElement(NULL)\r
-{\r
-}\r
-\r
-XMLElement::~XMLElement()\r
-{\r
- size_t i;\r
- for ( i = 0; i < attributes.size(); i++ )\r
- delete attributes[i];\r
- for ( i = 0; i < subElements.size(); i++ )\r
- delete subElements[i];\r
-}\r
-\r
-void\r
-XMLElement::AddSubElement ( XMLElement* e )\r
-{\r
- subElements.push_back ( e );\r
- e->parentElement = this;\r
-}\r
-\r
-// Parse()\r
-// This function takes a single xml tag ( i.e. beginning with '<' and\r
-// ending with '>', and parses out it's tag name and constituent\r
-// attributes.\r
-// Return Value: returns true if you need to look for a </tag> for\r
-// the one it just parsed...\r
-bool\r
-XMLElement::Parse(const string& token,\r
- bool& end_tag)\r
-{\r
- const char* p = token.c_str();\r
- assert ( *p == '<' );\r
- ++p;\r
- p += strspn ( p, WS );\r
-\r
- // check if this is a comment\r
- if ( !strncmp ( p, "!--", 3 ) )\r
- {\r
- name = "!--";\r
- end_tag = false;\r
- return false; // never look for end tag to a comment\r
- }\r
-\r
- end_tag = ( *p == '/' );\r
- if ( end_tag )\r
- {\r
- ++p;\r
- p += strspn ( p, WS );\r
- }\r
- const char* end = strpbrk ( p, WS );\r
- if ( !end )\r
- {\r
- end = strpbrk ( p, "/>" );\r
- assert ( end );\r
- }\r
- name = string ( p, end-p );\r
- p = end;\r
- p += strspn ( p, WS );\r
- while ( *p != '>' && *p != '/' )\r
- {\r
- end = strpbrk ( p, WSEQ );\r
- if ( !end )\r
- {\r
- end = strpbrk ( p, "/>" );\r
- assert ( end );\r
- }\r
- string attribute ( p, end-p ), value;\r
- p = end;\r
- p += strspn ( p, WS );\r
- if ( *p == '=' )\r
- {\r
- ++p;\r
- p += strspn ( p, WS );\r
- char quote = 0;\r
- if ( strchr ( "\"'", *p ) )\r
- {\r
- quote = *p++;\r
- end = strchr ( p, quote );\r
- }\r
- else\r
- {\r
- end = strpbrk ( p, WS );\r
- }\r
- if ( !end )\r
- {\r
- end = strchr ( p, '>' );\r
- assert(end);\r
- if ( end[-1] == '/' )\r
- end--;\r
- }\r
- value = string ( p, end-p );\r
- p = end;\r
- if ( quote && *p == quote )\r
- p++;\r
- p += strspn ( p, WS );\r
- }\r
- else if ( name[0] != '!' )\r
- {\r
- throw XMLSyntaxErrorException ( location,\r
- "attributes must have values" );\r
- }\r
- attributes.push_back ( new XMLAttribute ( attribute, value ) );\r
- }\r
- return !( *p == '/' ) && !end_tag;\r
-}\r
-\r
-XMLAttribute*\r
-XMLElement::GetAttribute ( const string& attribute,\r
- bool required )\r
-{\r
- // this would be faster with a tree-based container, but our attribute\r
- // lists are likely to stay so short as to not be an issue.\r
- for ( size_t i = 0; i < attributes.size(); i++ )\r
- {\r
- if ( attribute == attributes[i]->name )\r
- return attributes[i];\r
- }\r
- if ( required )\r
- {\r
- throw RequiredAttributeNotFoundException ( location,\r
- attribute,\r
- name );\r
- }\r
- return NULL;\r
-}\r
-\r
-const XMLAttribute*\r
-XMLElement::GetAttribute ( const string& attribute,\r
- bool required ) const\r
-{\r
- // this would be faster with a tree-based container, but our attribute\r
- // lists are likely to stay so short as to not be an issue.\r
- for ( size_t i = 0; i < attributes.size(); i++ )\r
- {\r
- if ( attribute == attributes[i]->name )\r
- return attributes[i];\r
- }\r
- if ( required )\r
- {\r
- throw RequiredAttributeNotFoundException ( location,\r
- attribute,\r
- name );\r
- }\r
- return NULL;\r
-}\r
-\r
-// XMLParse()\r
-// This function reads a "token" from the file loaded in XMLFile\r
-// if it finds a tag that is non-singular, it parses sub-elements and/or\r
-// inner text into the XMLElement that it is building to return.\r
-// Return Value: an XMLElement allocated via the new operator that contains\r
-// it's parsed data. Keep calling this function until it returns NULL\r
-// (no more data)\r
-XMLElement*\r
-XMLParse ( XMLFile& f,\r
- XMLIncludes* includes,\r
- const Path& path,\r
- bool* pend_tag = NULL )\r
-{\r
- string token;\r
- if ( !f.get_token(token) )\r
- return NULL;\r
- bool end_tag, is_include = false;\r
-\r
- while ( token[0] != '<'\r
- || !strncmp ( token.c_str (), "<!--", 4 )\r
- || !strncmp ( token.c_str (), "<?", 2 ) )\r
- {\r
- if ( token[0] != '<' )\r
- throw XMLSyntaxErrorException ( f.Location (),\r
- "expecting xml tag, not '%s'",\r
- token.c_str () );\r
- if ( !f.get_token(token) )\r
- return NULL;\r
- }\r
-\r
- XMLElement* e = new XMLElement ( f.Location () );\r
- bool bNeedEnd = e->Parse ( token, end_tag );\r
-\r
- if ( e->name == "xi:include" && includes )\r
- {\r
- XMLAttribute* att;\r
- att = e->GetAttribute ( "href", true );\r
- assert ( att );\r
- string includeFile ( path.Fixup ( att->value, true ) );\r
- string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );\r
- includes->push_back ( new XMLInclude ( e, path, topIncludeFile ) );\r
- is_include = true;\r
- }\r
-\r
- if ( !bNeedEnd )\r
- {\r
- if ( pend_tag )\r
- *pend_tag = end_tag;\r
- else if ( end_tag )\r
- {\r
- delete e;\r
- throw XMLSyntaxErrorException ( f.Location (),\r
- "end tag '%s' not expected",\r
- token.c_str() );\r
- return NULL;\r
- }\r
- return e;\r
- }\r
- bool bThisMixingErrorReported = false;\r
- while ( f.more_tokens () )\r
- {\r
- if ( f.next_is_text () )\r
- {\r
- if ( !f.get_token ( token ) || token.size () == 0 )\r
- {\r
- throw InvalidBuildFileException (\r
- f.Location(),\r
- "internal tool error - get_token() failed when more_tokens() returned true" );\r
- break;\r
- }\r
- if ( e->subElements.size() && !bThisMixingErrorReported )\r
- {\r
- throw XMLSyntaxErrorException ( f.Location (),\r
- "mixing of inner text with sub elements" );\r
- bThisMixingErrorReported = true;\r
- }\r
- if ( strchr ( token.c_str (), '>' ) )\r
- {\r
- throw XMLSyntaxErrorException ( f.Location (),\r
- "invalid symbol '>'" );\r
- }\r
- if ( e->value.size() > 0 )\r
- {\r
- throw XMLSyntaxErrorException ( f.Location (),\r
- "multiple instances of inner text" );\r
- e->value += " " + token;\r
- }\r
- else\r
- e->value = token;\r
- }\r
- else\r
- {\r
- XMLElement* e2 = XMLParse ( f, is_include ? NULL : includes, path, &end_tag );\r
- if ( !e2 )\r
- {\r
- throw InvalidBuildFileException (\r
- e->location,\r
- "end of file found looking for end tag" );\r
- break;\r
- }\r
- if ( end_tag )\r
- {\r
- if ( e->name != e2->name )\r
- {\r
- delete e2;\r
- throw XMLSyntaxErrorException ( f.Location (),\r
- "end tag name mismatch" );\r
- break;\r
- }\r
- delete e2;\r
- break;\r
- }\r
- if ( e->value.size () > 0 && !bThisMixingErrorReported )\r
- {\r
- throw XMLSyntaxErrorException ( f.Location (),\r
- "mixing of inner text with sub elements" );\r
- bThisMixingErrorReported = true;\r
- }\r
- e->AddSubElement ( e2 );\r
- }\r
- }\r
- return e;\r
-}\r
-\r
-void\r
-XMLReadFile ( XMLFile& f, XMLElement& head, XMLIncludes& includes, const Path& path )\r
-{\r
- for ( ;; )\r
- {\r
- XMLElement* e = XMLParse ( f, &includes, path );\r
- if ( !e )\r
- return;\r
- head.AddSubElement ( e );\r
- }\r
-}\r
-\r
-XMLElement*\r
-XMLLoadInclude ( XMLInclude& include,\r
- XMLIncludes& includes )\r
-{\r
- XMLAttribute* att;\r
- att = include.e->GetAttribute("href", true);\r
- assert(att);\r
-\r
- string file ( include.path.Fixup(att->value, true) );\r
- string top_file ( Path::RelativeFromWorkingDirectory ( file ) );\r
- include.e->attributes.push_back ( new XMLAttribute ( "top_href", top_file ) );\r
- XMLFile fInc;\r
- if ( !fInc.open ( file ) )\r
- {\r
- include.fileExists = false;\r
- // look for xi:fallback element\r
- for ( size_t i = 0; i < include.e->subElements.size (); i++ )\r
- {\r
- XMLElement* e2 = include.e->subElements[i];\r
- if ( e2->name == "xi:fallback" )\r
- {\r
- // now look for xi:include below...\r
- for ( i = 0; i < e2->subElements.size (); i++ )\r
- {\r
- XMLElement* e3 = e2->subElements[i];\r
- if ( e3->name == "xi:include" )\r
- {\r
- att = e3->GetAttribute ( "href", true );\r
- assert ( att );\r
- string includeFile ( include.path.Fixup ( att->value, true ) );\r
- string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );\r
- XMLInclude* fallbackInclude = new XMLInclude ( e3, include.path, topIncludeFile );\r
- return XMLLoadInclude ( *fallbackInclude, includes );\r
- }\r
- }\r
- throw InvalidBuildFileException (\r
- e2->location,\r
- "<xi:fallback> must have a <xi:include> sub-element" );\r
- return NULL;\r
- }\r
- }\r
- return NULL;\r
- }\r
- else\r
- {\r
- include.fileExists = true;\r
- XMLElement* new_e = new XMLElement ( include.e->location );\r
- new_e->name = "xi:included";\r
- Path path2 ( include.path, att->value );\r
- XMLReadFile ( fInc, *new_e, includes, path2 );\r
- return new_e;\r
- }\r
-}\r
-\r
-XMLElement*\r
-XMLLoadFile ( const string& filename,\r
- const Path& path,\r
- XMLIncludes& includes )\r
-{\r
- XMLFile f;\r
-\r
- if ( !f.open ( filename ) )\r
- throw FileNotFoundException ( filename );\r
-\r
- XMLElement* head = new XMLElement ( "(virtual)" );\r
-\r
- XMLReadFile ( f, *head, includes, path );\r
-\r
- for ( size_t i = 0; i < includes.size (); i++ )\r
- {\r
- XMLElement* e = includes[i]->e;\r
- XMLElement* e2 = XMLLoadInclude ( *includes[i], includes );\r
- if ( !e2 )\r
- {\r
- throw FileNotFoundException (\r
- ssprintf ( "%s (referenced from %s)",\r
- e->GetAttribute ( "top_href", true )->value.c_str (),\r
- f.Location ().c_str () ) );\r
- }\r
- XMLElement* parent = e->parentElement;\r
- XMLElement** parent_container = NULL;\r
- if ( !parent )\r
- {\r
- delete e;\r
- throw Exception ( "internal tool error: xi:include doesn't have a parent" );\r
- return NULL;\r
- }\r
- for ( size_t j = 0; j < parent->subElements.size (); j++ )\r
- {\r
- if ( parent->subElements[j] == e )\r
- {\r
- parent_container = &parent->subElements[j];\r
- break;\r
- }\r
- }\r
- if ( !parent_container )\r
- {\r
- delete e;\r
- throw Exception ( "internal tool error: couldn't find xi:include in parent's sub-elements" );\r
- return NULL;\r
- }\r
- // replace inclusion tree with the imported tree\r
- e2->parentElement = e->parentElement;\r
- e2->name = e->name;\r
- e2->attributes = e->attributes;\r
- *parent_container = e2;\r
- e->attributes.resize ( 0 );\r
- delete e;\r
- }\r
- return head;\r
-}\r
+/*
+ * Copyright (C) 2005 Casper S. Hornstrup
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "pch.h"
+
+#ifdef _MSC_VER
+#define MAX_PATH _MAX_PATH
+#endif
+
+#ifdef WIN32
+# include <direct.h>
+# include <io.h>
+#else
+# include <sys/stat.h>
+# define MAX_PATH PATH_MAX
+#endif
+#include <assert.h>
+
+#include "XML.h"
+#include "exception.h"
+#include "ssprintf.h"
+
+using std::string;
+using std::vector;
+
+#ifdef WIN32
+#define getcwd _getcwd
+#endif//WIN32
+
+static const char* WS = " \t\r\n";
+static const char* WSEQ = " =\t\r\n";
+
+string working_directory;
+
+XMLIncludes::~XMLIncludes()
+{
+ for ( size_t i = 0; i < this->size(); i++ )
+ delete (*this)[i];
+}
+
+void
+InitWorkingDirectory()
+{
+ // store the current directory for path calculations
+ working_directory.resize ( _MAX_PATH );
+ working_directory[0] = 0;
+ getcwd ( &working_directory[0], working_directory.size() );
+ working_directory.resize ( strlen ( working_directory.c_str() ) );
+}
+
+#ifdef _MSC_VER
+unsigned __int64
+#else
+unsigned long long
+#endif
+filelen ( FILE* f )
+{
+#ifdef WIN32
+ return _filelengthi64 ( _fileno(f) );
+#else
+ struct stat64 file_stat;
+ if ( fstat64(fileno(f), &file_stat) != 0 )
+ return 0;
+ return file_stat.st_size;
+#endif
+}
+
+Path::Path()
+{
+ if ( !working_directory.size() )
+ InitWorkingDirectory();
+ string s ( working_directory );
+ const char* p = strtok ( &s[0], "/\\" );
+ while ( p )
+ {
+ if ( *p )
+ path.push_back ( p );
+ p = strtok ( NULL, "/\\" );
+ }
+}
+
+Path::Path ( const Path& cwd, const string& file )
+{
+ string s ( cwd.Fixup ( file, false ) );
+ const char* p = strtok ( &s[0], "/\\" );
+ while ( p )
+ {
+ if ( *p )
+ path.push_back ( p );
+ p = strtok ( NULL, "/\\" );
+ }
+}
+
+string
+Path::Fixup ( const string& file, bool include_filename ) const
+{
+ if ( strchr ( "/\\", file[0] )
+#ifdef WIN32
+ // this squirreliness is b/c win32 has drive letters and *nix doesn't...
+ || file[1] == ':'
+#endif//WIN32
+ )
+ {
+ return file;
+ }
+ vector<string> pathtmp ( path );
+ string tmp ( file );
+ const char* prev = strtok ( &tmp[0], "/\\" );
+ const char* p = strtok ( NULL, "/\\" );
+ while ( p )
+ {
+ if ( !strcmp ( prev, "." ) )
+ ; // do nothing
+ else if ( !strcmp ( prev, ".." ) )
+ {
+ // this squirreliness is b/c win32 has drive letters and *nix doesn't...
+#ifdef WIN32
+ if ( pathtmp.size() > 1 )
+#else
+ if ( pathtmp.size() )
+#endif
+ pathtmp.resize ( pathtmp.size() - 1 );
+ }
+ else
+ pathtmp.push_back ( prev );
+ prev = p;
+ p = strtok ( NULL, "/\\" );
+ }
+ if ( include_filename )
+ pathtmp.push_back ( prev );
+
+ // reuse tmp variable to return recombined path
+ tmp.resize(0);
+ for ( size_t i = 0; i < pathtmp.size(); i++ )
+ {
+ // this squirreliness is b/c win32 has drive letters and *nix doesn't...
+#ifdef WIN32
+ if ( i ) tmp += "/";
+#else
+ tmp += "/";
+#endif
+ tmp += pathtmp[i];
+ }
+ return tmp;
+}
+
+string
+Path::RelativeFromWorkingDirectory ()
+{
+ string out = "";
+ for ( size_t i = 0; i < path.size(); i++ )
+ {
+ out += "/" + path[i];
+ }
+ return RelativeFromWorkingDirectory ( out );
+}
+
+string
+Path::RelativeFromWorkingDirectory ( const string& path )
+{
+ return Path::RelativeFromDirectory ( path, working_directory );
+}
+
+string
+Path::RelativeFromDirectory (
+ const string& path,
+ const string& base_directory )
+{
+ vector<string> vbase, vpath, vout;
+ Path::Split ( vbase, base_directory, true );
+ Path::Split ( vpath, path, true );
+#ifdef WIN32
+ // this squirreliness is b/c win32 has drive letters and *nix doesn't...
+ // not possible to do relative across different drive letters
+ if ( vbase[0] != vpath[0] )
+ return path;
+#endif
+ size_t i = 0;
+ while ( i < vbase.size() && i < vpath.size() && vbase[i] == vpath[i] )
+ ++i;
+ if ( vbase.size() == vpath.size() && i == vpath.size() )
+ return ".";
+ if ( i < vbase.size() )
+ {
+ // path goes above our base directory, we will need some ..'s
+ for ( size_t j = 0; j < i; j++ )
+ vout.push_back ( ".." );
+ }
+ while ( i < vpath.size() )
+ vout.push_back ( vpath[i++] );
+
+ // now merge vout into a string again
+ string out = vout[0];
+ for ( i = 1; i < vout.size(); i++ )
+ {
+ out += "/" + vout[i];
+ }
+ return out;
+}
+
+void
+Path::Split ( vector<string>& out,
+ const string& path,
+ bool include_last )
+{
+ string s ( path );
+ const char* prev = strtok ( &s[0], "/\\" );
+ const char* p = strtok ( NULL, "/\\" );
+ out.resize ( 0 );
+ while ( p )
+ {
+ out.push_back ( prev );
+ prev = p;
+ p = strtok ( NULL, "/\\" );
+ }
+ if ( include_last )
+ out.push_back ( prev );
+}
+
+XMLFile::XMLFile()
+{
+}
+
+void
+XMLFile::close()
+{
+ _buf.resize(0);
+ _p = _end = NULL;
+}
+
+bool
+XMLFile::open(const string& filename_)
+{
+ close();
+ FILE* f = fopen ( filename_.c_str(), "rb" );
+ if ( !f )
+ return false;
+ unsigned long len = (unsigned long)filelen(f);
+ _buf.resize ( len );
+ fread ( &_buf[0], 1, len, f );
+ fclose ( f );
+ _p = _buf.c_str();
+ _end = _p + len;
+ _filename = filename_;
+ next_token();
+ return true;
+}
+
+// next_token() moves the pointer to next token, which may be
+// an xml element or a text element, basically it's a glorified
+// skipspace, normally the user of this class won't need to call
+// this function
+void
+XMLFile::next_token()
+{
+ _p += strspn ( _p, WS );
+}
+
+bool
+XMLFile::next_is_text()
+{
+ return *_p != '<';
+}
+
+bool
+XMLFile::more_tokens()
+{
+ return _p != _end;
+}
+
+// get_token() is used to return a token, and move the pointer
+// past the token
+bool
+XMLFile::get_token(string& token)
+{
+ const char* tokend;
+ if ( !strncmp ( _p, "<!--", 4 ) )
+ {
+ tokend = strstr ( _p, "-->" );
+ if ( !tokend )
+ tokend = _end;
+ else
+ tokend += 3;
+ }
+ else if ( !strncmp ( _p, "<?", 2 ) )
+ {
+ tokend = strstr ( _p, "?>" );
+ if ( !tokend )
+ tokend = _end;
+ else
+ tokend += 2;
+ }
+ else if ( *_p == '<' )
+ {
+ tokend = strchr ( _p, '>' );
+ if ( !tokend )
+ tokend = _end;
+ else
+ ++tokend;
+ }
+ else
+ {
+ tokend = strchr ( _p, '<' );
+ if ( !tokend )
+ tokend = _end;
+ while ( tokend > _p && isspace(tokend[-1]) )
+ --tokend;
+ }
+ if ( tokend == _p )
+ return false;
+ token = string ( _p, tokend-_p );
+ _p = tokend;
+ next_token();
+ return true;
+}
+
+string
+XMLFile::Location() const
+{
+ int line = 1;
+ const char* p = strchr ( _buf.c_str(), '\n' );
+ while ( p && p < _p )
+ {
+ ++line;
+ p = strchr ( p+1, '\n' );
+ }
+ return ssprintf ( "%s(%i)",_filename.c_str(), line );
+}
+
+XMLAttribute::XMLAttribute()
+{
+}
+
+XMLAttribute::XMLAttribute(const string& name_,
+ const string& value_)
+ : name(name_), value(value_)
+{
+}
+
+XMLAttribute::XMLAttribute ( const XMLAttribute& src )
+ : name(src.name), value(src.value)
+{
+
+}
+
+XMLAttribute& XMLAttribute::operator = ( const XMLAttribute& src )
+{
+ name = src.name;
+ value = src.value;
+ return *this;
+}
+
+XMLElement::XMLElement ( XMLFile* xmlFile,
+ const string& location )
+ : xmlFile ( xmlFile ),
+ location ( location ),
+ parentElement ( NULL )
+{
+}
+
+XMLElement::~XMLElement()
+{
+ size_t i;
+ for ( i = 0; i < attributes.size(); i++ )
+ delete attributes[i];
+ for ( i = 0; i < subElements.size(); i++ )
+ delete subElements[i];
+}
+
+void
+XMLElement::AddSubElement ( XMLElement* e )
+{
+ subElements.push_back ( e );
+ e->parentElement = this;
+}
+
+// Parse()
+// This function takes a single xml tag ( i.e. beginning with '<' and
+// ending with '>', and parses out it's tag name and constituent
+// attributes.
+// Return Value: returns true if you need to look for a </tag> for
+// the one it just parsed...
+bool
+XMLElement::Parse(const string& token,
+ bool& end_tag)
+{
+ const char* p = token.c_str();
+ assert ( *p == '<' );
+ ++p;
+ p += strspn ( p, WS );
+
+ // check if this is a comment
+ if ( !strncmp ( p, "!--", 3 ) )
+ {
+ name = "!--";
+ end_tag = false;
+ return false; // never look for end tag to a comment
+ }
+
+ end_tag = ( *p == '/' );
+ if ( end_tag )
+ {
+ ++p;
+ p += strspn ( p, WS );
+ }
+ const char* end = strpbrk ( p, WS );
+ if ( !end )
+ {
+ end = strpbrk ( p, "/>" );
+ assert ( end );
+ }
+ name = string ( p, end-p );
+ p = end;
+ p += strspn ( p, WS );
+ while ( *p != '>' && *p != '/' )
+ {
+ end = strpbrk ( p, WSEQ );
+ if ( !end )
+ {
+ end = strpbrk ( p, "/>" );
+ assert ( end );
+ }
+ string attribute ( p, end-p ), value;
+ p = end;
+ p += strspn ( p, WS );
+ if ( *p == '=' )
+ {
+ ++p;
+ p += strspn ( p, WS );
+ char quote = 0;
+ if ( strchr ( "\"'", *p ) )
+ {
+ quote = *p++;
+ end = strchr ( p, quote );
+ }
+ else
+ {
+ end = strpbrk ( p, WS );
+ }
+ if ( !end )
+ {
+ end = strchr ( p, '>' );
+ assert(end);
+ if ( end[-1] == '/' )
+ end--;
+ }
+ value = string ( p, end-p );
+ p = end;
+ if ( quote && *p == quote )
+ p++;
+ p += strspn ( p, WS );
+ }
+ else if ( name[0] != '!' )
+ {
+ throw XMLSyntaxErrorException ( location,
+ "attributes must have values" );
+ }
+ attributes.push_back ( new XMLAttribute ( attribute, value ) );
+ }
+ return !( *p == '/' ) && !end_tag;
+}
+
+XMLAttribute*
+XMLElement::GetAttribute ( const string& attribute,
+ bool required )
+{
+ // this would be faster with a tree-based container, but our attribute
+ // lists are likely to stay so short as to not be an issue.
+ for ( size_t i = 0; i < attributes.size(); i++ )
+ {
+ if ( attribute == attributes[i]->name )
+ return attributes[i];
+ }
+ if ( required )
+ {
+ throw RequiredAttributeNotFoundException ( location,
+ attribute,
+ name );
+ }
+ return NULL;
+}
+
+const XMLAttribute*
+XMLElement::GetAttribute ( const string& attribute,
+ bool required ) const
+{
+ // this would be faster with a tree-based container, but our attribute
+ // lists are likely to stay so short as to not be an issue.
+ for ( size_t i = 0; i < attributes.size(); i++ )
+ {
+ if ( attribute == attributes[i]->name )
+ return attributes[i];
+ }
+ if ( required )
+ {
+ throw RequiredAttributeNotFoundException ( location,
+ attribute,
+ name );
+ }
+ return NULL;
+}
+
+// XMLParse()
+// This function reads a "token" from the file loaded in XMLFile
+// if it finds a tag that is non-singular, it parses sub-elements and/or
+// inner text into the XMLElement that it is building to return.
+// Return Value: an XMLElement allocated via the new operator that contains
+// it's parsed data. Keep calling this function until it returns NULL
+// (no more data)
+XMLElement*
+XMLParse ( XMLFile& f,
+ XMLIncludes* includes,
+ const Path& path,
+ bool* pend_tag = NULL )
+{
+ string token;
+ if ( !f.get_token(token) )
+ return NULL;
+ bool end_tag, is_include = false;
+
+ while ( token[0] != '<'
+ || !strncmp ( token.c_str (), "<!--", 4 )
+ || !strncmp ( token.c_str (), "<?", 2 ) )
+ {
+ if ( token[0] != '<' )
+ throw XMLSyntaxErrorException ( f.Location (),
+ "expecting xml tag, not '%s'",
+ token.c_str () );
+ if ( !f.get_token(token) )
+ return NULL;
+ }
+
+ XMLElement* e = new XMLElement ( &f,
+ f.Location () );
+ bool bNeedEnd = e->Parse ( token, end_tag );
+
+ if ( e->name == "xi:include" && includes )
+ {
+ XMLAttribute* att;
+ att = e->GetAttribute ( "href", true );
+ assert ( att );
+ string includeFile ( path.Fixup ( att->value, true ) );
+ string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );
+ includes->push_back ( new XMLInclude ( e, path, topIncludeFile ) );
+ is_include = true;
+ }
+
+ if ( !bNeedEnd )
+ {
+ if ( pend_tag )
+ *pend_tag = end_tag;
+ else if ( end_tag )
+ {
+ delete e;
+ throw XMLSyntaxErrorException ( f.Location (),
+ "end tag '%s' not expected",
+ token.c_str() );
+ return NULL;
+ }
+ return e;
+ }
+ bool bThisMixingErrorReported = false;
+ while ( f.more_tokens () )
+ {
+ if ( f.next_is_text () )
+ {
+ if ( !f.get_token ( token ) || token.size () == 0 )
+ {
+ throw InvalidBuildFileException (
+ f.Location(),
+ "internal tool error - get_token() failed when more_tokens() returned true" );
+ break;
+ }
+ if ( e->subElements.size() && !bThisMixingErrorReported )
+ {
+ throw XMLSyntaxErrorException ( f.Location (),
+ "mixing of inner text with sub elements" );
+ bThisMixingErrorReported = true;
+ }
+ if ( strchr ( token.c_str (), '>' ) )
+ {
+ throw XMLSyntaxErrorException ( f.Location (),
+ "invalid symbol '>'" );
+ }
+ if ( e->value.size() > 0 )
+ {
+ throw XMLSyntaxErrorException ( f.Location (),
+ "multiple instances of inner text" );
+ e->value += " " + token;
+ }
+ else
+ e->value = token;
+ }
+ else
+ {
+ XMLElement* e2 = XMLParse ( f, is_include ? NULL : includes, path, &end_tag );
+ if ( !e2 )
+ {
+ throw InvalidBuildFileException (
+ e->location,
+ "end of file found looking for end tag" );
+ break;
+ }
+ if ( end_tag )
+ {
+ if ( e->name != e2->name )
+ {
+ delete e2;
+ throw XMLSyntaxErrorException ( f.Location (),
+ "end tag name mismatch" );
+ break;
+ }
+ delete e2;
+ break;
+ }
+ if ( e->value.size () > 0 && !bThisMixingErrorReported )
+ {
+ throw XMLSyntaxErrorException ( f.Location (),
+ "mixing of inner text with sub elements" );
+ bThisMixingErrorReported = true;
+ }
+ e->AddSubElement ( e2 );
+ }
+ }
+ return e;
+}
+
+void
+XMLReadFile ( XMLFile& f, XMLElement& head, XMLIncludes& includes, const Path& path )
+{
+ for ( ;; )
+ {
+ XMLElement* e = XMLParse ( f, &includes, path );
+ if ( !e )
+ return;
+ head.AddSubElement ( e );
+ }
+}
+
+XMLElement*
+XMLLoadInclude ( XMLInclude& include,
+ XMLIncludes& includes )
+{
+ XMLAttribute* att;
+ att = include.e->GetAttribute("href", true);
+ assert(att);
+
+ string file ( include.path.Fixup(att->value, true) );
+ string top_file ( Path::RelativeFromWorkingDirectory ( file ) );
+ include.e->attributes.push_back ( new XMLAttribute ( "top_href", top_file ) );
+ XMLFile* fInc = new XMLFile();
+ if ( !fInc->open ( file ) )
+ {
+ include.fileExists = false;
+ // look for xi:fallback element
+ for ( size_t i = 0; i < include.e->subElements.size (); i++ )
+ {
+ XMLElement* e2 = include.e->subElements[i];
+ if ( e2->name == "xi:fallback" )
+ {
+ // now look for xi:include below...
+ for ( i = 0; i < e2->subElements.size (); i++ )
+ {
+ XMLElement* e3 = e2->subElements[i];
+ if ( e3->name == "xi:include" )
+ {
+ att = e3->GetAttribute ( "href", true );
+ assert ( att );
+ string includeFile ( include.path.Fixup ( att->value, true ) );
+ string topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile ) );
+ XMLInclude* fallbackInclude = new XMLInclude ( e3, include.path, topIncludeFile );
+ return XMLLoadInclude ( *fallbackInclude, includes );
+ }
+ }
+ throw InvalidBuildFileException (
+ e2->location,
+ "<xi:fallback> must have a <xi:include> sub-element" );
+ return NULL;
+ }
+ }
+ return NULL;
+ }
+ else
+ {
+ include.fileExists = true;
+ XMLElement* new_e = new XMLElement ( fInc,
+ include.e->location );
+ new_e->name = "xi:included";
+ Path path2 ( include.path, att->value );
+ XMLReadFile ( *fInc, *new_e, includes, path2 );
+ return new_e;
+ }
+}
+
+XMLElement*
+XMLLoadFile ( const string& filename,
+ const Path& path,
+ XMLIncludes& includes )
+{
+ XMLFile* f = new XMLFile();
+
+ if ( !f->open ( filename ) )
+ throw FileNotFoundException ( filename );
+
+ XMLElement* head = new XMLElement ( f,
+ "(virtual)" );
+
+ XMLReadFile ( *f, *head, includes, path );
+
+ for ( size_t i = 0; i < includes.size (); i++ )
+ {
+ XMLElement* e = includes[i]->e;
+ XMLElement* e2 = XMLLoadInclude ( *includes[i], includes );
+ if ( !e2 )
+ {
+ throw FileNotFoundException (
+ ssprintf ( "%s (referenced from %s)",
+ e->GetAttribute ( "top_href", true )->value.c_str (),
+ f->Location ().c_str () ) );
+ }
+ XMLElement* parent = e->parentElement;
+ XMLElement** parent_container = NULL;
+ if ( !parent )
+ {
+ delete e;
+ throw Exception ( "internal tool error: xi:include doesn't have a parent" );
+ return NULL;
+ }
+ for ( size_t j = 0; j < parent->subElements.size (); j++ )
+ {
+ if ( parent->subElements[j] == e )
+ {
+ parent_container = &parent->subElements[j];
+ break;
+ }
+ }
+ if ( !parent_container )
+ {
+ delete e;
+ throw Exception ( "internal tool error: couldn't find xi:include in parent's sub-elements" );
+ return NULL;
+ }
+ // replace inclusion tree with the imported tree
+ e2->parentElement = e->parentElement;
+ e2->name = e->name;
+ e2->attributes = e->attributes;
+ *parent_container = e2;
+ e->attributes.resize ( 0 );
+ delete e;
+ }
+ return head;
+}