CloverBootloader/rEFIt_UEFI/cpp_lib/XmlLiteParser.cpp

529 lines
16 KiB
C++

/*
*
* Copyright (c) 2020 Jief
* All rights reserved.
*
*/
// Only use angled for Platform, else, xcode project won't compile
#include "XmlLiteSimpleTypes.h"
#include "XmlLiteParser.h"
#include "../cpp_foundation/XString.h"
#include "../cpp_foundation/XStringArray.h"
#include "../cpp_foundation/unicode_conversions.h"
#ifdef _MSC_VER
#define __PRETTY_FUNCTION__ __FUNCSIG__
#endif
XBool strnnIsEqual(const char* key, size_t keyLength, const char* value, size_t valueLength)
{
if ( keyLength != valueLength ) return false;
return strncmp(key, value, keyLength) == 0;
}
XBool strnIsEqual(const char* key, size_t keyLength, const char* value)
{
return strnnIsEqual(key, keyLength, value, strlen(value));
}
XBool strnnIsEqualIC(const char* key, size_t keyLength, const char* value, size_t valueLength)
{
if ( keyLength != valueLength ) return false;
return strncasecmp(key, value, keyLength) == 0;
}
XBool strnIsEqualIC(const char* key, size_t keyLength, const char* value)
{
return strnnIsEqualIC(key, keyLength, value, strlen(value));
}
XmlParserPosition XmlLiteParser::getPosition()
{
return currentPos;
}
void XmlLiteParser::restorePosition(XmlParserPosition& xml_position)
{
currentPos = xml_position;
}
void XmlLiteParser::init(const char* buf, size_t size)
{
(void)size;
p_start = (char*)malloc(size+1);
memcpy(p_start, buf, size); // TODO remove that copy. I shouldnt need an ending 0 anymore, I think... Check.
p_start[size] = 0;
p_end = p_start + size;
currentPos.p = p_start;
currentPos.line = 1;
currentPos.col = 1;
XmlParserMessageArray.setEmpty();
for ( size_t i = 0; i < size ; ++i) {
if ( p_start[i] == 0 ) {
addWarning(true, S8Printf("Invalid NULL char at offset %zu. Replace by a space", i));
p_start[i] = 0x20; //replace random zero bytes to spaces
}
}
}
char XmlLiteParser::getchar()
{
return *currentPos.p;
}
char XmlLiteParser::moveForward()
{
if ( currentPos.p >= p_end ) {
return 0;
}
if ( getchar() == '\n' ) {
currentPos.line++;
currentPos.col = 1;
}else{
currentPos.col++;
}
return *( ++(currentPos.p) );
}
char XmlLiteParser::moveForward(int n)
{
for ( int i = 0 ; i < n-1 ; i++ ) moveForward();
return moveForward();
}
char XmlLiteParser::moveBackward()
{
if ( currentPos.p == p_start ) return 0;
currentPos.p--;
if ( getchar() == '\n' ) {
if ( currentPos.col != 1 ) panic("%s : currentPos.col != 1", __PRETTY_FUNCTION__);
currentPos.line--;
currentPos.col = 1;
char* q = currentPos.p;
while ( q > p_start && *(--q) != '\n' ) { currentPos.col++; };
}else{
currentPos.col--;
}
return getchar();
}
char XmlLiteParser::moveForwardUntilSignificant()
{
char c = getchar();
while ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) {
c = moveForward();
}
return c;
}
char XmlLiteParser::moveBackwardUntilSignificant()
{
char c = getchar();
while ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) {
c = moveBackward();
}
return c;
}
char XmlLiteParser::moveForwardUntil(char until)
{
char c = getchar();
while ( currentPos.p < p_end && c != until ) c = moveForward();
#ifdef JIEF_DEBUG
if ( currentPos.p > p_end ) panic("%s : currentPos.p > p_end", __PRETTY_FUNCTION__);
#endif
if ( currentPos.p == p_end ) return 0;
return c;
}
char XmlLiteParser::moveBackwardUntil(char until)
{
char c = moveBackward();
while ( c && c != until ) c = moveBackward();
return c;
}
char XmlLiteParser::moveForwardPastNext(char until)
{
moveForwardUntil(until);
return moveForward();
}
void XmlLiteParser::skipHeader()
{
if ( strncmp(currentPos.p, "<?xml", 5) == 0 ) {
moveForwardPastNext('>');
moveForwardUntilSignificant();
if ( strncmp(currentPos.p, "<!DOCTYPE", 9) == 0 ) {
moveForwardPastNext('>');
moveForwardUntilSignificant();
}
if ( strncmp(currentPos.p, "<plist", 6) == 0 ) {
moveForwardPastNext('>');
moveForwardUntilSignificant();
}
}
}
#define IS_TAGCHAR(x) ( ( x >= 'a' && x <='z' ) || ( x >= 'A' && x <='Z' ) || ( x >= '0' && x <= '9' ) )
XBool XmlLiteParser::getNextTag(const char** tag, size_t* length, XBool* isOpeningTag, XBool* isClosingTag, XBool generateErrors)
{
if (tag == NULL) panic("tag == NULL");
// // Find the start of the tag.
// moveUntilNext('<');
moveForwardUntilSignificant();
// if ( getchar() == '\0' ) {
if ( currentPos.p >= p_end ) {
addXmlError(generateErrors, S8Printf("Unexpected end of file at line %d col %d", currentPos.line, currentPos.col));
return false;
}
if ( getchar() == '<' ) {
moveForward();
if ( getchar() == '/') {
*isOpeningTag = false;
*isClosingTag = true;
moveForward();
}else{
*isOpeningTag = true;
*isClosingTag = false;
}
*tag = currentPos.p;
// Find the end of the tag.
char c = moveForward();
while ( IS_TAGCHAR(c) ) c = moveForward();
if ( c == '/' ) {
if ( *isClosingTag ) {
// tag like </true/> are illegal
addXmlError(generateErrors, S8Printf("unexpected '/' at line %d col %d. Tag like </true/> are illegal.", currentPos.line, currentPos.col));
return false;
}
*length = size_t(currentPos.p - *tag);
return true;
}
if ( c != '>' ) {
if ( c == '\0' ) {
addXmlError(generateErrors, S8Printf("Unexpected end of file at line %d col %d while looking for closing char '>'", currentPos.line, currentPos.col));
return false;
}
addXmlError(generateErrors, S8Printf("Unexpected char '%c' at line %d col %d while looking for closing char '>'", c, currentPos.line, currentPos.col));
return false;
}
*length = size_t(currentPos.p - *tag);
moveForward();
return true;
}else
if ( getchar() == '/' ) {
// special case where we left at an empty tag <true/>
XmlParserPosition pos = getPosition();
moveBackwardUntil('<');
moveForward();
*tag = currentPos.p;
restorePosition(pos);
*length = size_t(currentPos.p - *tag);
char c = moveForward(); // skip '/'
if ( c != '>' ) {
if ( c == '\0' ) {
addXmlError(generateErrors, S8Printf("Unexpected end of file at line %d col %d while looking for closing char '>'", currentPos.line, currentPos.col));
return false;
}
addXmlError(generateErrors, S8Printf("Unexpected char '%c' at line %d col %d while looking for closing char '>'", c, currentPos.line, currentPos.col));
return false;
}
moveForward();
*isOpeningTag = false;
*isClosingTag = true;
return true;
}else{
addXmlError(generateErrors, S8Printf("Unexpected char '%c' at line %d col %d while looking for a tag that must start with '<'", getchar() , currentPos.line, currentPos.col));
moveForward();
return false;
}
}
/*
* Get the string from current position until the next '<', or end of buffer.
* Spaces at begining or end are skipped.
* If no '<', false is returned.
* Position at the '<' when exit.
*/
void XmlLiteParser::getString(const char** string, size_t* length)
{
if ( *currentPos.p == '/' && *(currentPos.p+1) == '>' ) {
// Special case. We were left at '/>' to represent an empty tag
*string = NULL;
*length = 0;
return;
}
char c;
// c = moveForwardUntilSignificant();
c = getchar();
// if ( c == 0 || c == '<' /*|| c == '/'*/ ) {
if ( currentPos.p >= p_end || c == '<' /*|| c == '/'*/ ) {
*string = NULL;
*length = 0;
return;
}
*string = currentPos.p;
c = moveForwardUntil('<');
if ( c ) {
moveBackward();
}
// moveBackwardUntilSignificant();
*length = size_t(currentPos.p - *string + 1);
if ( *length == 0 ) *string = NULL;
moveForwardUntil('<');
}
#define RETURN_IF_FALSE(Expression) do { XBool b = Expression; if ( !b ) return false; } while (0);
XBool XmlLiteParser::getSimpleTag(const char** tag, size_t* tagLength, const char** value, size_t* valueLength, const char* expectedTag/*, XBool valueCanBeEmpty*/, XBool generateErrors)
{
XBool isOpeningTag, isClosingTag;
XmlParserPosition pos = getPosition();
RETURN_IF_FALSE( getNextTag(tag, tagLength, &isOpeningTag, &isClosingTag, generateErrors) );
if ( isClosingTag ) {
// opening tag expected
addXmlError(generateErrors, S8Printf("Unexpected closing tag '%.*s' at line %d col %d. Was expecting <key>.", (int)*tagLength, *tag, pos.line, pos.col));
return false;
}
if ( expectedTag && !strnIsEqual(*tag, *tagLength, expectedTag) ) {
addXmlError(generateErrors, S8Printf("Unexpected tag '%.*s' at line %d col %d. Was expecting <%s>.", (int)*tagLength, *tag, pos.line, pos.col, expectedTag));
return false;
}
getString(value, valueLength);
// if ( !valueCanBeEmpty && *valueLength == 0 ) {
// if ( generateErrors ) addError(S8Printf("Text of tag '%.*s' cannot be empty at line %d col %d\n", (int)*tagLength, *tag, currentPos.line, currentPos.col));
// return false;
// }
const char* endTag;
size_t endTagLength;
pos = getPosition();
RETURN_IF_FALSE( getNextTag(&endTag, &endTagLength, &isOpeningTag, &isClosingTag, generateErrors) );
if ( isOpeningTag ) {
// closing tag expected
currentPos = pos;
addXmlError(generateErrors, S8Printf("Expected closing tag '%.*s' at line %d col %d", (int)*tagLength, *tag, pos.line, pos.col));
return false;
}
if ( !strnnIsEqual(endTag, endTagLength, *tag, *tagLength) ) {
// closing tag name is different
currentPos = pos;
addXmlError(generateErrors, S8Printf("Expected closing tag '%.*s' at line %d col %d", (int)*tagLength, *tag, pos.line, pos.col));
return false;
}
// if ( *valueLength == 0 ) {
// addError(generateErrors, S8Printf("Key '%.*s' has an empty value at line %d col %d\n", (int)*tagLength, *tag, pos.line, pos.col));
// return false;
// }
return true;
}
/*
* The opening tag has been read. Skip until the closing tag
*/
XBool XmlLiteParser::skipUntilClosingTag(const char* tagToSkip, size_t tagToSkipLength, XBool generateErrors)
{
const char* value;
size_t valueLength;
XBool b;
getString(&value, &valueLength);
//TODO
while ( valueLength > 0 && (*value == ' ' || *value == '\t' || *value == '\r' || *value == '\n') ) {
++value;
--valueLength;
}
const char* tag;
size_t tagLength;
XBool isOpeningTag, isClosingTag;
XmlParserPosition pos = getPosition();
b = getNextTag(&tag, &tagLength, &isOpeningTag, &isClosingTag, generateErrors);
if ( !b ) return false;
if ( isOpeningTag && valueLength > 0 ) {
// cannot have a tag containing text AND subtag
addXmlError(generateErrors, S8Printf("Tag '%.*s' cannot contains text AND tag at line %d col %d", (int)tagToSkipLength, tagToSkip, pos.line, pos.col));
return false;
}
while ( isOpeningTag )
{
b = skipUntilClosingTag(tag, tagLength, generateErrors);
if ( !b ) return false;
b = getNextTag(&tag, &tagLength, &isOpeningTag, &isClosingTag, generateErrors);
if ( !b ) return false;
}
if ( !strnnIsEqual(tag, tagLength, tagToSkip, tagToSkipLength) ) {
// closing tag is different
addXmlError(generateErrors, S8Printf("Expected closing tag '%.*s' at line %d col %d", (int)tagToSkipLength, tagToSkip, pos.line, pos.col));
return false;
}
char c = moveForwardUntilSignificant();
if ( c != 0 && c != '<' ) {
// After a closing tag, we can't have chars other than spaces
addXmlError(generateErrors, S8Printf("Unexpected char '%c' at line %d col %d after the end of tag '%.*s'. The containing tag cannot have text abd tab", getchar() , currentPos.line, currentPos.col, (int)tagToSkipLength, tagToSkip));
return false;
}
return true;
}
XBool XmlLiteParser::skipNextTag(XBool generateErrors)
{
const char* tag;
size_t tagLength;
XBool isOpeningTag, isClosingTag;
XBool b;
XmlParserPosition pos = getPosition();
b = getNextTag(&tag, &tagLength, &isOpeningTag, &isClosingTag, generateErrors);
if ( !b ) return false;
if ( isClosingTag ) {
// next tag should be an opening one
addXmlError(generateErrors, S8Printf("Expected an opening tag at line %d col %d", pos.line, pos.col));
return false;
}
return skipUntilClosingTag(tag, tagLength, generateErrors);
}
XBool XmlLiteParser::getSimpleTagValue(const char* expectedTag, size_t expectedTagLength, const char** value, size_t* valueLength, XmlParserPosition* xmlParserPosition, XBool generateErrors)
{
const char* tag;
size_t tagLength;
*xmlParserPosition = getPosition();
XBool b = getSimpleTag(&tag, &tagLength, value, valueLength, expectedTag, generateErrors);
if ( !b ) return false;
if ( !strnnIsEqualIC(tag, tagLength, expectedTag, expectedTagLength) ) {
addXmlError(generateErrors, S8Printf("Expecting a <%s> at line %d col %d", expectedTag, (*xmlParserPosition).line, (*xmlParserPosition).col));
return false;
}
return true;
}
XBool XmlLiteParser::getKeyTagValue(const char** value, size_t* valueLength, XmlParserPosition* xmlParserPosition, XBool generateErrors)
{
const char* tag;
size_t tagLength;
#ifdef DEBUG_TRACE
#endif
moveForwardUntilSignificant(); // to get the position more accurate
*xmlParserPosition = getPosition();
XBool b = getSimpleTag(&tag, &tagLength, value, valueLength, "key", generateErrors);
if ( !b ) {
currentPos = *xmlParserPosition;
return false;
}
#ifdef DEBUG_TRACE
printf("XmlLiteParser::getKeyTagValue key=%.*s, line=%d, buffer=", (int)*valueLength, *value, (*xmlParserPosition).getLine());
for(size_t i=0 ; i<40 ; i++) printf("%c", (*xmlParserPosition).p[i] < 32 ? 0 : (*xmlParserPosition).p[i]);
printf("\n");
#endif
// I think the following cannot happen anymore...
if ( !strnIsEqualIC(tag, tagLength, "key") ) {
addXmlError(generateErrors, S8Printf("Expecting a <key> at line %d col %d", (*xmlParserPosition).line, (*xmlParserPosition).col));
currentPos = *xmlParserPosition;
return false;
}
return true;
}
XBool XmlLiteParser::consumeOpeningTag(const char* expectedTag, XBool generateErrors)
{
const char* tag;
size_t length;
XBool isOpeningTag, isClosingTag;
auto pos = currentPos;
RETURN_IF_FALSE ( getNextTag(&tag, &length, &isOpeningTag, &isClosingTag, generateErrors) );
if ( !strnIsEqual(tag, length, expectedTag) ) {
addXmlError(generateErrors, S8Printf("Expecting tag '%s' at line %d.", expectedTag, pos.getLine()));
currentPos = pos;
return false;
}
if ( isClosingTag ) {
addXmlError(generateErrors, S8Printf("Expecting opening tag '%s' at line %d.", expectedTag, pos.getLine()));
currentPos = pos;
return false;
}
return true;
}
XBool XmlLiteParser::consumeClosingTag(const char* expectedTag, XBool generateErrors)
{
const char* tag;
size_t length;
XBool isOpeningTag, isClosingTag;
auto pos = currentPos;
RETURN_IF_FALSE ( getNextTag(&tag, &length, &isOpeningTag, &isClosingTag, generateErrors) );
if ( !strnIsEqual(tag, length, expectedTag) ) {
addXmlError(true, S8Printf("Expecting tag '%s' at line %d.", expectedTag, pos.getLine()));
return false;
}
if ( isOpeningTag ) {
addXmlError(true, S8Printf("Expecting closing tag '%s' at line %d.", expectedTag, pos.getLine()));
return false;
}
return true;
}
XBool XmlLiteParser::nextTagIsOpeningTag(const char* expectedTag)
{
const char* tag;
size_t length;
XBool isOpeningTag, isClosingTag;
XmlParserPosition pos = getPosition();
RETURN_IF_FALSE ( getNextTag(&tag, &length, &isOpeningTag, &isClosingTag, false) );
restorePosition(pos);
if ( !strnIsEqual(tag, length, expectedTag) ) return false;
if ( isClosingTag ) return false;
return true;
}
XBool XmlLiteParser::nextTagIsClosingTag(const char* expectedTag)
{
const char* tag;
size_t length;
XBool isOpeningTag, isClosingTag;
XmlParserPosition pos = getPosition();
RETURN_IF_FALSE ( getNextTag(&tag, &length, &isOpeningTag, &isClosingTag, false) );
restorePosition(pos);
if ( !strnIsEqual(tag, length, expectedTag) ) return false;
if ( isOpeningTag ) return false;
return true;
}