6 #define MAX_PATH _MAX_PATH
13 # include <sys/stat.h>
14 # define MAX_PATH PATH_MAX
19 #include "exception.h"
26 #define getcwd _getcwd
29 static const char* WS
= " \t\r\n";
30 static const char* WSEQ
= " =\t\r\n";
32 string working_directory
;
34 XMLIncludes::~XMLIncludes()
36 for ( size_t i
= 0; i
< this->size(); i
++ )
41 InitWorkingDirectory()
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() ) );
58 return _filelengthi64 ( _fileno(f
) );
60 struct stat64 file_stat
;
61 if ( fstat64(fileno(f
), &file_stat
) != 0 )
63 return file_stat
.st_size
;
69 if ( !working_directory
.size() )
70 InitWorkingDirectory();
71 string
s ( working_directory
);
72 const char* p
= strtok ( &s
[0], "/\\" );
77 p
= strtok ( NULL
, "/\\" );
81 Path::Path ( const Path
& cwd
, const string
& file
)
83 string
s ( cwd
.Fixup ( file
, false ) );
84 const char* p
= strtok ( &s
[0], "/\\" );
89 p
= strtok ( NULL
, "/\\" );
94 Path::Fixup ( const string
& file
, bool include_filename
) const
96 if ( strchr ( "/\\", file
[0] )
98 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
105 vector
<string
> pathtmp ( path
);
107 const char* prev
= strtok ( &tmp
[0], "/\\" );
108 const char* p
= strtok ( NULL
, "/\\" );
111 if ( !strcmp ( prev
, "." ) )
113 else if ( !strcmp ( prev
, ".." ) )
115 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
117 if ( pathtmp
.size() > 1 )
119 if ( pathtmp
.size() )
121 pathtmp
.resize ( pathtmp
.size() - 1 );
124 pathtmp
.push_back ( prev
);
126 p
= strtok ( NULL
, "/\\" );
128 if ( include_filename
)
129 pathtmp
.push_back ( prev
);
131 // reuse tmp variable to return recombined path
133 for ( size_t i
= 0; i
< pathtmp
.size(); i
++ )
135 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
147 Path::RelativeFromWorkingDirectory ()
150 for ( size_t i
= 0; i
< path
.size(); i
++ )
152 out
+= "/" + path
[i
];
154 return RelativeFromWorkingDirectory ( out
);
158 Path::RelativeFromWorkingDirectory ( const string
& path
)
160 vector
<string
> vwork
, vpath
, vout
;
161 Path::Split ( vwork
, working_directory
, true );
162 Path::Split ( vpath
, path
, true );
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] )
170 while ( i
< vwork
.size() && i
< vpath
.size() && vwork
[i
] == vpath
[i
] )
172 if ( i
< vwork
.size() )
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 ( ".." );
178 while ( i
< vpath
.size() )
179 vout
.push_back ( vpath
[i
++] );
181 // now merge vout into a string again
182 string out
= vout
[0];
183 for ( i
= 1; i
< vout
.size(); i
++ )
185 out
+= "/" + vout
[i
];
191 Path::Split ( vector
<string
>& out
,
196 const char* prev
= strtok ( &s
[0], "/\\" );
197 const char* p
= strtok ( NULL
, "/\\" );
201 out
.push_back ( prev
);
203 p
= strtok ( NULL
, "/\\" );
206 out
.push_back ( prev
);
221 XMLFile::open(const string
& filename_
)
224 FILE* f
= fopen ( filename_
.c_str(), "rb" );
227 unsigned long len
= (unsigned long)filelen(f
);
229 fread ( &_buf
[0], 1, len
, f
);
233 _filename
= filename_
;
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
243 XMLFile::next_token()
245 _p
+= strspn ( _p
, WS
);
249 XMLFile::next_is_text()
255 XMLFile::more_tokens()
260 // get_token() is used to return a token, and move the pointer
263 XMLFile::get_token(string
& token
)
266 if ( !strncmp ( _p
, "<!--", 4 ) )
268 tokend
= strstr ( _p
, "-->" );
274 else if ( !strncmp ( _p
, "<?", 2 ) )
276 tokend
= strstr ( _p
, "?>" );
282 else if ( *_p
== '<' )
284 tokend
= strchr ( _p
, '>' );
292 tokend
= strchr ( _p
, '<' );
295 while ( tokend
> _p
&& isspace(tokend
[-1]) )
300 token
= string ( _p
, tokend
-_p
);
307 XMLFile::Location() const
310 const char* p
= strchr ( _buf
.c_str(), '\n' );
311 while ( p
&& p
< _p
)
314 p
= strchr ( p
+1, '\n' );
316 return ssprintf ( "%s(%i)",_filename
.c_str(), line
);
319 XMLAttribute::XMLAttribute()
323 XMLAttribute::XMLAttribute(const string
& name_
,
324 const string
& value_
)
325 : name(name_
), value(value_
)
329 XMLAttribute::XMLAttribute ( const XMLAttribute
& src
)
330 : name(src
.name
), value(src
.value
)
335 XMLAttribute
& XMLAttribute::operator = ( const XMLAttribute
& src
)
342 XMLElement::XMLElement ( const string
& location_
)
343 : location(location_
),
348 XMLElement::~XMLElement()
351 for ( i
= 0; i
< attributes
.size(); i
++ )
352 delete attributes
[i
];
353 for ( i
= 0; i
< subElements
.size(); i
++ )
354 delete subElements
[i
];
358 XMLElement::AddSubElement ( XMLElement
* e
)
360 subElements
.push_back ( e
);
361 e
->parentElement
= this;
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
368 // Return Value: returns true if you need to look for a </tag> for
369 // the one it just parsed...
371 XMLElement::Parse(const string
& token
,
374 const char* p
= token
.c_str();
375 assert ( *p
== '<' );
377 p
+= strspn ( p
, WS
);
379 // check if this is a comment
380 if ( !strncmp ( p
, "!--", 3 ) )
384 return false; // never look for end tag to a comment
387 end_tag
= ( *p
== '/' );
391 p
+= strspn ( p
, WS
);
393 const char* end
= strpbrk ( p
, WS
);
396 end
= strpbrk ( p
, "/>" );
399 name
= string ( p
, end
-p
);
401 p
+= strspn ( p
, WS
);
402 while ( *p
!= '>' && *p
!= '/' )
404 end
= strpbrk ( p
, WSEQ
);
407 end
= strpbrk ( p
, "/>" );
410 string
attribute ( p
, end
-p
), value
;
412 p
+= strspn ( p
, WS
);
416 p
+= strspn ( p
, WS
);
418 if ( strchr ( "\"'", *p
) )
421 end
= strchr ( p
, quote
);
425 end
= strpbrk ( p
, WS
);
429 end
= strchr ( p
, '>' );
431 if ( end
[-1] == '/' )
434 value
= string ( p
, end
-p
);
436 if ( quote
&& *p
== quote
)
438 p
+= strspn ( p
, WS
);
440 else if ( name
[0] != '!' )
442 throw XMLSyntaxErrorException ( location
,
443 "attributes must have values" );
445 attributes
.push_back ( new XMLAttribute ( attribute
, value
) );
447 return !( *p
== '/' ) && !end_tag
;
451 XMLElement::GetAttribute ( const string
& attribute
,
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
++ )
458 if ( attribute
== attributes
[i
]->name
)
459 return attributes
[i
];
463 throw RequiredAttributeNotFoundException ( location
,
471 XMLElement::GetAttribute ( const string
& attribute
,
472 bool required
) const
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
++ )
478 if ( attribute
== attributes
[i
]->name
)
479 return attributes
[i
];
483 throw RequiredAttributeNotFoundException ( location
,
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
498 XMLParse ( XMLFile
& f
,
499 XMLIncludes
* includes
,
501 bool* pend_tag
= NULL
)
504 if ( !f
.get_token(token
) )
506 bool end_tag
, is_include
= false;
508 while ( token
[0] != '<'
509 || !strncmp ( token
.c_str (), "<!--", 4 )
510 || !strncmp ( token
.c_str (), "<?", 2 ) )
512 if ( token
[0] != '<' )
513 throw XMLSyntaxErrorException ( f
.Location (),
514 "expecting xml tag, not '%s'",
516 if ( !f
.get_token(token
) )
520 XMLElement
* e
= new XMLElement ( f
.Location () );
521 bool bNeedEnd
= e
->Parse ( token
, end_tag
);
523 if ( e
->name
== "xi:include" && includes
)
526 att
= e
->GetAttribute ( "href", true );
528 string
includeFile ( path
.Fixup ( att
->value
, true ) );
529 string
topIncludeFile ( Path::RelativeFromWorkingDirectory ( includeFile
) );
530 includes
->push_back ( new XMLInclude ( e
, path
, topIncludeFile
) );
541 throw XMLSyntaxErrorException ( f
.Location (),
542 "end tag '%s' not expected",
548 bool bThisMixingErrorReported
= false;
549 while ( f
.more_tokens () )
551 if ( f
.next_is_text () )
553 if ( !f
.get_token ( token
) || token
.size () == 0 )
555 throw InvalidBuildFileException (
557 "internal tool error - get_token() failed when more_tokens() returned true" );
560 if ( e
->subElements
.size() && !bThisMixingErrorReported
)
562 throw XMLSyntaxErrorException ( f
.Location (),
563 "mixing of inner text with sub elements" );
564 bThisMixingErrorReported
= true;
566 if ( strchr ( token
.c_str (), '>' ) )
568 throw XMLSyntaxErrorException ( f
.Location (),
569 "invalid symbol '>'" );
571 if ( e
->value
.size() > 0 )
573 throw XMLSyntaxErrorException ( f
.Location (),
574 "multiple instances of inner text" );
575 e
->value
+= " " + token
;
582 XMLElement
* e2
= XMLParse ( f
, is_include
? NULL
: includes
, path
, &end_tag
);
585 throw InvalidBuildFileException (
587 "end of file found looking for end tag" );
592 if ( e
->name
!= e2
->name
)
595 throw XMLSyntaxErrorException ( f
.Location (),
596 "end tag name mismatch" );
602 if ( e
->value
.size () > 0 && !bThisMixingErrorReported
)
604 throw XMLSyntaxErrorException ( f
.Location (),
605 "mixing of inner text with sub elements" );
606 bThisMixingErrorReported
= true;
608 e
->AddSubElement ( e2
);
615 XMLReadFile ( XMLFile
& f
, XMLElement
& head
, XMLIncludes
& includes
, const Path
& path
)
619 XMLElement
* e
= XMLParse ( f
, &includes
, path
);
622 head
.AddSubElement ( e
);
627 XMLLoadInclude ( XMLInclude
& include
,
628 XMLIncludes
& includes
)
631 att
= include
.e
->GetAttribute("href", true);
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
) );
638 if ( !fInc
.open ( file
) )
640 include
.fileExists
= false;
641 // look for xi:fallback element
642 for ( size_t i
= 0; i
< include
.e
->subElements
.size (); i
++ )
644 XMLElement
* e2
= include
.e
->subElements
[i
];
645 if ( e2
->name
== "xi:fallback" )
647 // now look for xi:include below...
648 for ( i
= 0; i
< e2
->subElements
.size (); i
++ )
650 XMLElement
* e3
= e2
->subElements
[i
];
651 if ( e3
->name
== "xi:include" )
653 att
= e3
->GetAttribute ( "href", true );
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
);
661 throw InvalidBuildFileException (
663 "<xi:fallback> must have a <xi:include> sub-element" );
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
);
681 XMLLoadFile ( const string
& filename
,
683 XMLIncludes
& includes
)
687 if ( !f
.open ( filename
) )
688 throw FileNotFoundException ( filename
);
690 XMLElement
* head
= new XMLElement ( "(virtual)" );
692 XMLReadFile ( f
, *head
, includes
, path
);
694 for ( size_t i
= 0; i
< includes
.size (); i
++ )
696 XMLElement
* e
= includes
[i
]->e
;
697 XMLElement
* e2
= XMLLoadInclude ( *includes
[i
], includes
);
700 throw FileNotFoundException (
701 ssprintf ( "%s (referenced from %s)",
702 e
->GetAttribute ( "top_href", true )->value
.c_str (),
703 f
.Location ().c_str () ) );
705 XMLElement
* parent
= e
->parentElement
;
706 XMLElement
** parent_container
= NULL
;
710 throw Exception ( "internal tool error: xi:include doesn't have a parent" );
713 for ( size_t j
= 0; j
< parent
->subElements
.size (); j
++ )
715 if ( parent
->subElements
[j
] == e
)
717 parent_container
= &parent
->subElements
[j
];
721 if ( !parent_container
)
724 throw Exception ( "internal tool error: couldn't find xi:include in parent's sub-elements" );
727 // replace inclusion tree with the imported tree
728 e2
->parentElement
= e
->parentElement
;
730 e2
->attributes
= e
->attributes
;
731 *parent_container
= e2
;
732 e
->attributes
.resize ( 0 );