15 #include "exception.h"
22 #define getcwd _getcwd
25 static const char* WS
= " \t\r\n";
26 static const char* WSEQ
= " =\t\r\n";
28 string working_directory
;
36 XMLInclude ( XMLElement
* e_
, const Path
& path_
)
42 class XMLIncludes
: public vector
<XMLInclude
*>
47 for ( size_t i
= 0; i
< this->size(); i
++ )
53 InitWorkingDirectory()
55 // store the current directory for path calculations
56 working_directory
.resize ( _MAX_PATH
);
57 working_directory
[0] = 0;
58 getcwd ( &working_directory
[0], working_directory
.size() );
59 working_directory
.resize ( strlen ( working_directory
.c_str() ) );
70 return _filelengthi64 ( _fileno(f
) );
72 struct stat64 file_stat
;
73 if ( fstat64(fileno(f
), &file_stat
) != 0 )
75 return file_stat
.st_size
;
81 if ( !working_directory
.size() )
82 InitWorkingDirectory();
83 string
s ( working_directory
);
84 const char* p
= strtok ( &s
[0], "/\\" );
89 p
= strtok ( NULL
, "/\\" );
93 Path::Path ( const Path
& cwd
, const string
& file
)
95 string
s ( cwd
.Fixup ( file
, false ) );
96 const char* p
= strtok ( &s
[0], "/\\" );
100 path
.push_back ( p
);
101 p
= strtok ( NULL
, "/\\" );
106 Path::Fixup ( const string
& file
, bool include_filename
) const
108 if ( strchr ( "/\\", file
[0] )
110 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
117 vector
<string
> pathtmp ( path
);
119 const char* prev
= strtok ( &tmp
[0], "/\\" );
120 const char* p
= strtok ( NULL
, "/\\" );
123 if ( !strcmp ( prev
, "." ) )
125 else if ( !strcmp ( prev
, ".." ) )
127 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
129 if ( pathtmp
.size() > 1 )
131 if ( pathtmp
.size() )
133 pathtmp
.resize ( pathtmp
.size() - 1 );
136 pathtmp
.push_back ( prev
);
138 p
= strtok ( NULL
, "/\\" );
140 if ( include_filename
)
141 pathtmp
.push_back ( prev
);
143 // reuse tmp variable to return recombined path
145 for ( size_t i
= 0; i
< pathtmp
.size(); i
++ )
147 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
159 Path::RelativeFromWorkingDirectory ( const string
& path
)
161 vector
<string
> vwork
, vpath
, vout
;
162 Path::Split ( vwork
, working_directory
, true );
163 Path::Split ( vpath
, path
, true );
165 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
166 // not possible to do relative across different drive letters
167 if ( vwork
[0] != vpath
[0] )
171 while ( i
< vwork
.size() && i
< vpath
.size() && vwork
[i
] == vpath
[i
] )
173 if ( i
< vwork
.size() )
175 // path goes above our working directory, we will need some ..'s
176 for ( size_t j
= 0; j
< i
; j
++ )
177 vout
.push_back ( ".." );
179 while ( i
< vpath
.size() )
180 vout
.push_back ( vpath
[i
++] );
182 // now merge vout into a string again
184 for ( i
= 0; i
< vout
.size(); i
++ )
186 // this squirreliness is b/c win32 has drive letters and *nix doesn't...
198 Path::Split ( vector
<string
>& out
,
203 const char* prev
= strtok ( &s
[0], "/\\" );
204 const char* p
= strtok ( NULL
, "/\\" );
208 out
.push_back ( prev
);
210 p
= strtok ( NULL
, "/\\" );
213 out
.push_back ( prev
);
228 XMLFile::open(const string
& filename_
)
231 FILE* f
= fopen ( filename_
.c_str(), "rb" );
234 unsigned long len
= (unsigned long)filelen(f
);
236 fread ( &_buf
[0], 1, len
, f
);
240 _filename
= filename_
;
245 // next_token() moves the pointer to next token, which may be
246 // an xml element or a text element, basically it's a glorified
247 // skipspace, normally the user of this class won't need to call
250 XMLFile::next_token()
252 _p
+= strspn ( _p
, WS
);
256 XMLFile::next_is_text()
262 XMLFile::more_tokens()
267 // get_token() is used to return a token, and move the pointer
270 XMLFile::get_token(string
& token
)
273 if ( !strncmp ( _p
, "<!--", 4 ) )
275 tokend
= strstr ( _p
, "-->" );
281 else if ( !strncmp ( _p
, "<?", 2 ) )
283 tokend
= strstr ( _p
, "?>" );
289 else if ( *_p
== '<' )
291 tokend
= strchr ( _p
, '>' );
299 tokend
= strchr ( _p
, '<' );
302 while ( tokend
> _p
&& isspace(tokend
[-1]) )
307 token
= string ( _p
, tokend
-_p
);
314 XMLFile::Location() const
317 const char* p
= strchr ( _buf
.c_str(), '\n' );
318 while ( p
&& p
< _p
)
321 p
= strchr ( p
+1, '\n' );
323 return ssprintf ( "%s(%i)",_filename
.c_str(), line
);
326 XMLAttribute::XMLAttribute()
330 XMLAttribute::XMLAttribute(const string
& name_
,
331 const string
& value_
)
332 : name(name_
), value(value_
)
336 XMLAttribute::XMLAttribute ( const XMLAttribute
& src
)
337 : name(src
.name
), value(src
.value
)
342 XMLAttribute
& XMLAttribute::operator = ( const XMLAttribute
& src
)
349 XMLElement::XMLElement ( const string
& location_
)
350 : location(location_
),
355 XMLElement::~XMLElement()
358 for ( i
= 0; i
< attributes
.size(); i
++ )
359 delete attributes
[i
];
360 for ( i
= 0; i
< subElements
.size(); i
++ )
361 delete subElements
[i
];
365 XMLElement::AddSubElement ( XMLElement
* e
)
367 subElements
.push_back ( e
);
368 e
->parentElement
= this;
372 // This function takes a single xml tag ( i.e. beginning with '<' and
373 // ending with '>', and parses out it's tag name and constituent
375 // Return Value: returns true if you need to look for a </tag> for
376 // the one it just parsed...
378 XMLElement::Parse(const string
& token
,
381 const char* p
= token
.c_str();
382 assert ( *p
== '<' );
384 p
+= strspn ( p
, WS
);
386 // check if this is a comment
387 if ( !strncmp ( p
, "!--", 3 ) )
391 return false; // never look for end tag to a comment
394 end_tag
= ( *p
== '/' );
398 p
+= strspn ( p
, WS
);
400 const char* end
= strpbrk ( p
, WS
);
403 end
= strpbrk ( p
, "/>" );
406 name
= string ( p
, end
-p
);
408 p
+= strspn ( p
, WS
);
409 while ( *p
!= '>' && *p
!= '/' )
411 end
= strpbrk ( p
, WSEQ
);
414 end
= strpbrk ( p
, "/>" );
417 string
attribute ( p
, end
-p
), value
;
419 p
+= strspn ( p
, WS
);
423 p
+= strspn ( p
, WS
);
425 if ( strchr ( "\"'", *p
) )
428 end
= strchr ( p
, quote
);
432 end
= strpbrk ( p
, WS
);
436 end
= strchr ( p
, '>' );
438 if ( end
[-1] == '/' )
441 value
= string ( p
, end
-p
);
443 if ( quote
&& *p
== quote
)
445 p
+= strspn ( p
, WS
);
447 else if ( name
[0] != '!' )
449 throw XMLSyntaxErrorException ( location
,
450 "attributes must have values" );
452 attributes
.push_back ( new XMLAttribute ( attribute
, value
) );
454 return !( *p
== '/' ) && !end_tag
;
458 XMLElement::GetAttribute ( const string
& attribute
,
461 // this would be faster with a tree-based container, but our attribute
462 // lists are likely to stay so short as to not be an issue.
463 for ( size_t i
= 0; i
< attributes
.size(); i
++ )
465 if ( attribute
== attributes
[i
]->name
)
466 return attributes
[i
];
470 throw RequiredAttributeNotFoundException ( location
,
478 XMLElement::GetAttribute ( const string
& attribute
,
479 bool required
) const
481 // this would be faster with a tree-based container, but our attribute
482 // lists are likely to stay so short as to not be an issue.
483 for ( size_t i
= 0; i
< attributes
.size(); i
++ )
485 if ( attribute
== attributes
[i
]->name
)
486 return attributes
[i
];
490 throw RequiredAttributeNotFoundException ( location
,
498 // This function reads a "token" from the file loaded in XMLFile
499 // if it finds a tag that is non-singular, it parses sub-elements and/or
500 // inner text into the XMLElement that it is building to return.
501 // Return Value: an XMLElement allocated via the new operator that contains
502 // it's parsed data. Keep calling this function until it returns NULL
506 XMLIncludes
* includes
,
508 bool* pend_tag
= NULL
)
511 if ( !f
.get_token(token
) )
513 bool end_tag
, is_include
= false;
515 while ( token
[0] != '<'
516 || !strncmp ( token
.c_str(), "<!--", 4 )
517 || !strncmp ( token
.c_str(), "<?", 2 ) )
519 if ( token
[0] != '<' )
520 throw XMLSyntaxErrorException ( f
.Location(),
521 "expecting xml tag, not '%s'",
523 if ( !f
.get_token(token
) )
527 XMLElement
* e
= new XMLElement ( f
.Location() );
528 bool bNeedEnd
= e
->Parse ( token
, end_tag
);
530 if ( e
->name
== "xi:include" && includes
)
532 includes
->push_back ( new XMLInclude ( e
, path
) );
543 throw XMLSyntaxErrorException ( f
.Location(),
544 "end tag '%s' not expected",
550 bool bThisMixingErrorReported
= false;
551 while ( f
.more_tokens() )
553 if ( f
.next_is_text() )
555 if ( !f
.get_token ( token
) || !token
.size() )
557 throw InvalidBuildFileException (
559 "internal tool error - get_token() failed when more_tokens() returned true" );
562 if ( e
->subElements
.size() && !bThisMixingErrorReported
)
564 throw XMLSyntaxErrorException ( f
.Location(),
565 "mixing of inner text with sub elements" );
566 bThisMixingErrorReported
= true;
568 if ( strchr ( token
.c_str(), '>' ) )
570 throw XMLSyntaxErrorException ( f
.Location(),
571 "invalid symbol '>'" );
573 if ( e
->value
.size() )
575 throw XMLSyntaxErrorException ( f
.Location(),
576 "multiple instances of inner text" );
577 e
->value
+= " " + token
;
584 XMLElement
* e2
= XMLParse ( f
, is_include
? NULL
: includes
, path
, &end_tag
);
587 throw InvalidBuildFileException (
589 "end of file found looking for end tag" );
594 if ( e
->name
!= e2
->name
)
597 throw XMLSyntaxErrorException ( f
.Location(),
598 "end tag name mismatch" );
604 if ( e
->value
.size() && !bThisMixingErrorReported
)
606 throw XMLSyntaxErrorException ( f
.Location(),
607 "mixing of inner text with sub elements" );
608 bThisMixingErrorReported
= true;
610 e
->AddSubElement ( e2
);
617 XMLReadFile ( XMLFile
& f
, XMLElement
& head
, XMLIncludes
& includes
, const Path
& path
)
621 XMLElement
* e
= XMLParse ( f
, &includes
, path
);
624 head
.AddSubElement ( e
);
629 XMLLoadInclude ( XMLElement
* e
, const Path
& path
, XMLIncludes
& includes
)
632 att
= e
->GetAttribute("href",true);
635 string
file ( path
.Fixup(att
->value
,true) );
636 string
top_file ( Path::RelativeFromWorkingDirectory ( file
) );
637 e
->attributes
.push_back ( new XMLAttribute ( "top_href", top_file
) );
639 if ( !fInc
.open ( file
) )
641 // look for xi:fallback element
642 for ( size_t i
= 0; i
< e
->subElements
.size(); i
++ )
644 XMLElement
* e2
= 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 return XMLLoadInclude ( e3
, path
, includes
);
656 throw InvalidBuildFileException (
658 "<xi:fallback> must have a <xi:include> sub-element" );
666 XMLElement
* new_e
= new XMLElement ( e
->location
);
667 new_e
->name
= "xi:included";
668 Path
path2 ( path
, att
->value
);
669 XMLReadFile ( fInc
, *new_e
, includes
, path2
);
675 XMLLoadFile ( const string
& filename
, const Path
& path
)
677 XMLIncludes includes
;
680 if ( !f
.open ( filename
) )
681 throw FileNotFoundException ( filename
);
683 XMLElement
* head
= new XMLElement("(virtual)");
685 XMLReadFile ( f
, *head
, includes
, path
);
687 for ( size_t i
= 0; i
< includes
.size(); i
++ )
689 XMLElement
* e
= includes
[i
]->e
;
690 XMLElement
* e2
= XMLLoadInclude ( includes
[i
]->e
, includes
[i
]->path
, includes
);
693 throw FileNotFoundException (
694 ssprintf("%s (referenced from %s)",
695 e
->GetAttribute("top_href",true)->value
.c_str(),
696 f
.Location().c_str() ) );
698 XMLElement
* parent
= e
->parentElement
;
699 XMLElement
** parent_container
= NULL
;
703 throw Exception ( "internal tool error: xi:include doesn't have a parent" );
706 for ( size_t j
= 0; j
< parent
->subElements
.size(); j
++ )
708 if ( parent
->subElements
[j
] == e
)
710 parent_container
= &parent
->subElements
[j
];
714 if ( !parent_container
)
717 throw Exception ( "internal tool error: couldn't find xi:include in parent's sub-elements" );
720 // replace inclusion tree with the imported tree
721 e2
->parentElement
= e
->parentElement
;
723 e2
->attributes
= e
->attributes
;
724 *parent_container
= e2
;
725 e
->attributes
.resize(0);