10 #include "exception.h"
17 #define getcwd _getcwd
20 static const char* WS
= " \t\r\n";
21 static const char* WSEQ
= " =\t\r\n";
23 string working_directory
;
26 InitWorkingDirectory()
28 // store the current directory for path calculations
29 working_directory
.resize ( _MAX_PATH
);
30 working_directory
[0] = 0;
31 getcwd ( &working_directory
[0], working_directory
.size() );
32 working_directory
.resize ( strlen ( working_directory
.c_str() ) );
43 return _filelengthi64 ( _fileno(f
) );
45 struct stat64 file_stat
;
46 if ( fstat64(fileno(f
), &file_stat
) != 0 )
48 return file_stat
.st_size
;
54 if ( !working_directory
.size() )
55 InitWorkingDirectory();
56 string
s ( working_directory
);
57 const char* p
= strtok ( &s
[0], "/\\" );
62 p
= strtok ( NULL
, "/\\" );
66 Path::Path ( const Path
& cwd
, const string
& file
)
68 string
s ( cwd
.Fixup ( file
, false ) );
69 const char* p
= strtok ( &s
[0], "/\\" );
74 p
= strtok ( NULL
, "/\\" );
79 Path::Fixup ( const string
& file
, bool include_filename
) const
81 if ( strchr ( "/\\", file
[0] )
83 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
90 vector
<string
> pathtmp ( path
);
92 const char* prev
= strtok ( &tmp
[0], "/\\" );
93 const char* p
= strtok ( NULL
, "/\\" );
96 if ( !strcmp ( prev
, "." ) )
98 else if ( !strcmp ( prev
, ".." ) )
100 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
102 if ( pathtmp
.size() > 1 )
104 if ( pathtmp
.size() )
106 pathtmp
.resize ( pathtmp
.size() - 1 );
109 pathtmp
.push_back ( prev
);
111 p
= strtok ( NULL
, "/\\" );
113 if ( include_filename
)
114 pathtmp
.push_back ( prev
);
116 // reuse tmp variable to return recombined path
118 for ( size_t i
= 0; i
< pathtmp
.size(); i
++ )
120 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
132 Path::RelativeFromWorkingDirectory ( const string
& path
)
134 vector
<string
> vwork
, vpath
, vout
;
135 Path::Split ( vwork
, working_directory
, true );
136 Path::Split ( vpath
, path
, true );
138 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
139 // not possible to do relative across different drive letters
140 if ( vwork
[0] != vpath
[0] )
144 while ( i
< vwork
.size() && i
< vpath
.size() && vwork
[i
] == vpath
[i
] )
146 if ( i
< vwork
.size() )
148 // path goes above our working directory, we will need some ..'s
149 for ( size_t j
= 0; j
< i
; j
++ )
150 vout
.push_back ( ".." );
152 while ( i
< vpath
.size() )
153 vout
.push_back ( vpath
[i
++] );
155 // now merge vout into a string again
157 for ( i
= 0; i
< vout
.size(); i
++ )
159 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
171 Path::Split ( vector
<string
>& out
,
176 const char* prev
= strtok ( &s
[0], "/\\" );
177 const char* p
= strtok ( NULL
, "/\\" );
181 out
.push_back ( prev
);
183 p
= strtok ( NULL
, "/\\" );
186 out
.push_back ( prev
);
201 XMLFile::open(const string
& filename_
)
204 FILE* f
= fopen ( filename_
.c_str(), "rb" );
207 unsigned long len
= (unsigned long)filelen(f
);
209 fread ( &_buf
[0], 1, len
, f
);
213 _filename
= filename_
;
218 // next_token() moves the pointer to next token, which may be
219 // an xml element or a text element, basically it's a glorified
220 // skipspace, normally the user of this class won't need to call
223 XMLFile::next_token()
225 _p
+= strspn ( _p
, WS
);
229 XMLFile::next_is_text()
235 XMLFile::more_tokens()
240 // get_token() is used to return a token, and move the pointer
243 XMLFile::get_token(string
& token
)
246 if ( !strncmp ( _p
, "<!--", 4 ) )
248 tokend
= strstr ( _p
, "-->" );
254 else if ( !strncmp ( _p
, "<?", 2 ) )
256 tokend
= strstr ( _p
, "?>" );
262 else if ( *_p
== '<' )
264 tokend
= strchr ( _p
, '>' );
272 tokend
= strchr ( _p
, '<' );
275 while ( tokend
> _p
&& isspace(tokend
[-1]) )
280 token
= string ( _p
, tokend
-_p
);
287 XMLFile::Location() const
290 const char* p
= strchr ( _buf
.c_str(), '\n' );
291 while ( p
&& p
< _p
)
294 p
= strchr ( p
+1, '\n' );
296 return ssprintf ( "%s(%i)",_filename
.c_str(), line
);
299 XMLAttribute::XMLAttribute()
303 XMLAttribute::XMLAttribute(const string
& name_
,
304 const string
& value_
)
305 : name(name_
), value(value_
)
309 XMLElement::XMLElement()
310 : parentElement(NULL
)
314 XMLElement::~XMLElement()
317 for ( i
= 0; i
< attributes
.size(); i
++ )
318 delete attributes
[i
];
319 for ( i
= 0; i
< subElements
.size(); i
++ )
320 delete subElements
[i
];
324 XMLElement::AddSubElement ( XMLElement
* e
)
326 subElements
.push_back ( e
);
327 e
->parentElement
= this;
331 // This function takes a single xml tag ( i.e. beginning with '<' and
332 // ending with '>', and parses out it's tag name and constituent
334 // Return Value: returns true if you need to look for a </tag> for
335 // the one it just parsed...
337 XMLElement::Parse(const string
& token
,
340 const char* p
= token
.c_str();
341 assert ( *p
== '<' );
343 p
+= strspn ( p
, WS
);
345 // check if this is a comment
346 if ( !strncmp ( p
, "!--", 3 ) )
350 return false; // never look for end tag to a comment
353 end_tag
= ( *p
== '/' );
357 p
+= strspn ( p
, WS
);
359 const char* end
= strpbrk ( p
, WS
);
362 end
= strpbrk ( p
, "/>" );
365 name
= string ( p
, end
-p
);
367 p
+= strspn ( p
, WS
);
368 while ( *p
!= '>' && *p
!= '/' )
370 end
= strpbrk ( p
, WSEQ
);
373 end
= strpbrk ( p
, "/>" );
376 string
attribute ( p
, end
-p
), value
;
378 p
+= strspn ( p
, WS
);
382 p
+= strspn ( p
, WS
);
384 if ( strchr ( "\"'", *p
) )
387 end
= strchr ( p
, quote
);
391 end
= strpbrk ( p
, WS
);
395 end
= strchr ( p
, '>' );
397 if ( end
[-1] == '/' )
400 value
= string ( p
, end
-p
);
402 if ( quote
&& *p
== quote
)
404 p
+= strspn ( p
, WS
);
406 attributes
.push_back ( new XMLAttribute ( attribute
, value
) );
408 return !( *p
== '/' ) && !end_tag
;
412 XMLElement::GetAttribute ( const string
& attribute
,
415 // this would be faster with a tree-based container, but our attribute
416 // lists are likely to stay so short as to not be an issue.
417 for ( size_t i
= 0; i
< attributes
.size(); i
++ )
419 if ( attribute
== attributes
[i
]->name
)
420 return attributes
[i
];
424 throw RequiredAttributeNotFoundException ( attribute
,
431 XMLElement::GetAttribute ( const string
& attribute
,
432 bool required
) const
434 // this would be faster with a tree-based container, but our attribute
435 // lists are likely to stay so short as to not be an issue.
436 for ( size_t i
= 0; i
< attributes
.size(); i
++ )
438 if ( attribute
== attributes
[i
]->name
)
439 return attributes
[i
];
443 throw RequiredAttributeNotFoundException ( attribute
,
450 // This function reads a "token" from the file loaded in XMLFile
451 // REM TODO FIXME: At the moment it can't handle comments or non-xml tags.
452 // if it finds a tag that is non-singular, it parses sub-elements and/or
453 // inner text into the XMLElement that it is building to return.
454 // Return Value: an XMLElement allocated via the new operator that contains
455 // it's parsed data. Keep calling this function until it returns NULL
460 bool* pend_tag
/*= NULL*/)
463 if ( !f
.get_token(token
) )
467 while ( token
[0] != '<'
468 || !strncmp ( token
.c_str(), "<!--", 4 )
469 || !strncmp ( token
.c_str(), "<?", 2 ) )
471 if ( token
[0] != '<' )
472 throw XMLSyntaxErrorException ( f
.Location(),
473 "expecting xml tag, not '%s'",
475 if ( !f
.get_token(token
) )
479 XMLElement
* e
= new XMLElement
;
480 bool bNeedEnd
= e
->Parse ( token
, end_tag
);
482 if ( e
->name
== "xi:include" )
485 att
= e
->GetAttribute("href",true);
488 string
file ( path
.Fixup(att
->value
,true) );
489 string
top_file ( Path::RelativeFromWorkingDirectory ( file
) );
490 e
->attributes
.push_back ( new XMLAttribute ( "top_href", top_file
) );
492 if ( !fInc
.open ( file
) )
493 throw FileNotFoundException (
494 ssprintf("%s (referenced from %s)",
496 f
.Location().c_str() ) );
499 Path
path2 ( path
, att
->value
);
502 XMLElement
* e2
= XMLParse ( fInc
, path2
);
505 e
->AddSubElement ( e2
);
517 throw XMLSyntaxErrorException ( f
.Location(),
518 "end tag '%s' not expected",
524 bool bThisMixingErrorReported
= false;
525 while ( f
.more_tokens() )
527 if ( f
.next_is_text() )
529 if ( !f
.get_token ( token
) || !token
.size() )
531 throw Exception ( "internal tool error - get_token() failed when more_tokens() returned true" );
534 if ( e
->subElements
.size() && !bThisMixingErrorReported
)
536 throw XMLSyntaxErrorException ( f
.Location(),
537 "mixing of inner text with sub elements" );
538 bThisMixingErrorReported
= true;
540 if ( strchr ( token
.c_str(), '>' ) )
542 throw XMLSyntaxErrorException ( f
.Location(),
543 "invalid symbol '>'" );
545 if ( e
->value
.size() )
547 throw XMLSyntaxErrorException ( f
.Location(),
548 "multiple instances of inner text" );
549 e
->value
+= " " + token
;
556 XMLElement
* e2
= XMLParse ( f
, path
, &end_tag
);
559 if ( e
->name
!= e2
->name
)
560 throw XMLSyntaxErrorException ( f
.Location(),
561 "end tag name mismatch" );
565 if ( e
->value
.size() && !bThisMixingErrorReported
)
567 throw XMLSyntaxErrorException ( f
.Location(),
568 "mixing of inner text with sub elements" );
569 bThisMixingErrorReported
= true;
571 e
->AddSubElement ( e2
);