Fixes and new apps (#30)
- 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
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
#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 "";
|
||||
}
|
||||
Reference in New Issue
Block a user