dbf850c434
- M5 Unit Modules library + M5 Unit Test app - Minor fixes for TodoList, TwoEleven, Snake, Brainfuck and Breakout - Fixed SerialConsole to use the uart controller as it was broken in one of the many updates - Fixed keyboard input in TwoEleven, Breakout, Magic8Ball and Snake - Bluetooth Media Keys app, supports pressing physical keys to trigger the corresponding buttonmatrix button - Epub Reader app
318 lines
8.1 KiB
C++
318 lines
8.1 KiB
C++
#include "SimpleXmlParser.h"
|
|
#include <tactility/log.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static const char* TAG = "SimpleXmlParser";
|
|
|
|
SimpleXmlParser::SimpleXmlParser() {
|
|
buffer_ = (uint8_t*)malloc(BUFFER_SIZE);
|
|
if (!buffer_) {
|
|
LOG_E(TAG, "Failed to allocate buffer");
|
|
abort();
|
|
}
|
|
}
|
|
|
|
SimpleXmlParser::~SimpleXmlParser() {
|
|
close();
|
|
if (buffer_) {
|
|
free(buffer_);
|
|
buffer_ = nullptr;
|
|
}
|
|
}
|
|
|
|
bool SimpleXmlParser::openFromMemory(const char* data, size_t dataSize) {
|
|
close();
|
|
if (!buffer_ || !data) return false;
|
|
|
|
memoryData_ = data;
|
|
memorySize_ = dataSize;
|
|
usingMemory_ = true;
|
|
|
|
bufferStartPos_ = 0;
|
|
bufferLen_ = 0;
|
|
filePos_ = 0;
|
|
currentNodeType_ = NodeType::None;
|
|
return true;
|
|
}
|
|
|
|
bool SimpleXmlParser::openFromStream(StreamCallback callback) {
|
|
close();
|
|
if (!buffer_ || !callback) return false;
|
|
|
|
streamCallback_ = callback;
|
|
usingStream_ = true;
|
|
streamPosition_ = 0;
|
|
streamEOF_ = false;
|
|
|
|
bufferStartPos_ = 0;
|
|
bufferLen_ = 0;
|
|
filePos_ = 0;
|
|
currentNodeType_ = NodeType::None;
|
|
return true;
|
|
}
|
|
|
|
void SimpleXmlParser::close() {
|
|
memoryData_ = nullptr;
|
|
memorySize_ = 0;
|
|
usingMemory_ = false;
|
|
streamCallback_ = nullptr;
|
|
usingStream_ = false;
|
|
streamPosition_ = 0;
|
|
streamEOF_ = false;
|
|
bufferStartPos_ = 0;
|
|
bufferLen_ = 0;
|
|
filePos_ = 0;
|
|
currentNodeType_ = NodeType::None;
|
|
}
|
|
|
|
bool SimpleXmlParser::loadBufferAround(size_t pos) {
|
|
if (usingStream_) {
|
|
// Simple streaming: we can only move forward
|
|
if (pos < bufferStartPos_) return false;
|
|
|
|
while (pos >= bufferStartPos_ + bufferLen_ && !streamEOF_) {
|
|
int bytesRead = streamCallback_((char*)buffer_, BUFFER_SIZE);
|
|
if (bytesRead <= 0) {
|
|
streamEOF_ = true;
|
|
return false;
|
|
}
|
|
bufferStartPos_ = streamPosition_;
|
|
bufferLen_ = bytesRead;
|
|
streamPosition_ += bytesRead;
|
|
}
|
|
return pos >= bufferStartPos_ && pos < bufferStartPos_ + bufferLen_;
|
|
}
|
|
|
|
if (usingMemory_) {
|
|
if (pos >= memorySize_) return false;
|
|
|
|
bufferStartPos_ = pos;
|
|
bufferLen_ = (memorySize_ - pos > BUFFER_SIZE) ? BUFFER_SIZE : (memorySize_ - pos);
|
|
memcpy(buffer_, memoryData_ + pos, bufferLen_);
|
|
return bufferLen_ > 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
char SimpleXmlParser::getByteAt(size_t pos) {
|
|
if (usingMemory_) {
|
|
return (pos < memorySize_) ? memoryData_[pos] : '\0';
|
|
}
|
|
if (bufferLen_ > 0 && pos >= bufferStartPos_ && pos < bufferStartPos_ + bufferLen_) {
|
|
return (char)buffer_[pos - bufferStartPos_];
|
|
}
|
|
if (loadBufferAround(pos)) {
|
|
return (char)buffer_[pos - bufferStartPos_];
|
|
}
|
|
return '\0';
|
|
}
|
|
|
|
char SimpleXmlParser::peekChar() {
|
|
return getByteAt(filePos_);
|
|
}
|
|
|
|
char SimpleXmlParser::readChar() {
|
|
char c = getByteAt(filePos_);
|
|
if (c != '\0') filePos_++;
|
|
return c;
|
|
}
|
|
|
|
bool SimpleXmlParser::skipWhitespace() {
|
|
while (true) {
|
|
char c = peekChar();
|
|
if (c == '\0') return false;
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
|
readChar();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SimpleXmlParser::matchString(const char* str) {
|
|
size_t len = strlen(str);
|
|
size_t savedFilePos = filePos_;
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (readChar() != str[i]) {
|
|
filePos_ = savedFilePos;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SimpleXmlParser::read() {
|
|
if (!usingMemory_ && !usingStream_) return false;
|
|
|
|
currentName_.clear();
|
|
currentValue_.clear();
|
|
isEmptyElement_ = false;
|
|
attributes_.clear();
|
|
|
|
while (true) {
|
|
char c = peekChar();
|
|
if (c == '\0') {
|
|
currentNodeType_ = NodeType::EndOfFile;
|
|
return false;
|
|
}
|
|
|
|
if (c == '<') {
|
|
readChar(); // consume '<'
|
|
char next = peekChar();
|
|
if (next == '/') {
|
|
return readEndElement();
|
|
} else if (next == '!') {
|
|
readChar(); // consume '!'
|
|
if (peekChar() == '-') return readComment();
|
|
if (matchString("[CDATA[")) return readCDATA();
|
|
skipToEndOfTag();
|
|
continue;
|
|
} else if (next == '?') {
|
|
return readProcessingInstruction();
|
|
} else {
|
|
return readElement();
|
|
}
|
|
} else {
|
|
if (readText()) return true;
|
|
// whitespace-only text node - continue outer loop
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SimpleXmlParser::readElement() {
|
|
currentNodeType_ = NodeType::Element;
|
|
currentName_ = readElementName();
|
|
parseAttributes();
|
|
skipWhitespace();
|
|
if (peekChar() == '/') {
|
|
readChar();
|
|
isEmptyElement_ = true;
|
|
}
|
|
skipToEndOfTag();
|
|
return true;
|
|
}
|
|
|
|
bool SimpleXmlParser::readEndElement() {
|
|
currentNodeType_ = NodeType::EndElement;
|
|
readChar(); // consume '/'
|
|
currentName_ = readElementName();
|
|
skipToEndOfTag();
|
|
return true;
|
|
}
|
|
|
|
bool SimpleXmlParser::readText() {
|
|
currentNodeType_ = NodeType::Text;
|
|
currentValue_.clear();
|
|
bool hasNonWhitespace = false;
|
|
while (true) {
|
|
char c = peekChar();
|
|
if (c == '\0' || c == '<') break;
|
|
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') hasNonWhitespace = true;
|
|
currentValue_ += readChar();
|
|
}
|
|
return hasNonWhitespace; // false = whitespace-only; caller (read()) continues looping
|
|
}
|
|
|
|
bool SimpleXmlParser::readComment() {
|
|
currentNodeType_ = NodeType::Comment;
|
|
readChar(); // consume '-'
|
|
if (readChar() != '-') {
|
|
skipToEndOfTag();
|
|
return false;
|
|
}
|
|
while (true) {
|
|
char c = readChar();
|
|
if (c == '\0') break;
|
|
if (c == '-' && peekChar() == '-') {
|
|
readChar();
|
|
if (peekChar() == '>') {
|
|
readChar();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SimpleXmlParser::readCDATA() {
|
|
currentNodeType_ = NodeType::CDATA;
|
|
while (true) {
|
|
char c = readChar();
|
|
if (c == '\0') break;
|
|
if (c == ']' && peekChar() == ']') {
|
|
size_t savedPos = filePos_;
|
|
readChar(); // tentatively consume second ']'
|
|
if (peekChar() == '>') {
|
|
readChar(); // consume '>'
|
|
break;
|
|
}
|
|
filePos_ = savedPos; // not end of CDATA - backtrack second ']'
|
|
}
|
|
currentValue_ += c;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SimpleXmlParser::readProcessingInstruction() {
|
|
currentNodeType_ = NodeType::ProcessingInstruction;
|
|
readChar(); // consume '?'
|
|
currentName_ = readElementName();
|
|
while (true) {
|
|
char c = readChar();
|
|
if (c == '\0') break;
|
|
if (c == '?' && peekChar() == '>') {
|
|
readChar();
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string SimpleXmlParser::readElementName() {
|
|
std::string name;
|
|
while (true) {
|
|
char c = peekChar();
|
|
if (c == '\0' || c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '>' || c == '/' || c == '=') break;
|
|
name += readChar();
|
|
}
|
|
return name;
|
|
}
|
|
|
|
void SimpleXmlParser::parseAttributes() {
|
|
while (true) {
|
|
skipWhitespace();
|
|
char c = peekChar();
|
|
if (c == '>' || c == '/' || c == '\0') break;
|
|
std::string name = readElementName();
|
|
if (name.empty()) break;
|
|
skipWhitespace();
|
|
if (readChar() != '=') break;
|
|
skipWhitespace();
|
|
char quote = readChar();
|
|
if (quote != '"' && quote != '\'') break;
|
|
std::string value;
|
|
while (true) {
|
|
char vc = readChar();
|
|
if (vc == '\0' || vc == quote) break;
|
|
value += vc;
|
|
}
|
|
attributes_.push_back({name, value});
|
|
}
|
|
}
|
|
|
|
void SimpleXmlParser::skipToEndOfTag() {
|
|
while (true) {
|
|
char c = readChar();
|
|
if (c == '>' || c == '\0') break;
|
|
}
|
|
}
|
|
|
|
std::string SimpleXmlParser::getAttribute(const char* name) const {
|
|
for (const auto& attr : attributes_) {
|
|
if (attr.name == name) return attr.value;
|
|
}
|
|
return "";
|
|
}
|