You've already forked crosspoint-reader
mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-04-29 10:26:52 -07:00
ce1756e36f
## Summary **What is the goal of this PR?** Added `destroyXmlParser()` helper to replace the repeated 4-line parser cleanup block (stop, clear callbacks, free, null) that was copyied across 6 XML parser files. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_
177 lines
5.6 KiB
C++
177 lines
5.6 KiB
C++
#include "OpdsParser.h"
|
|
|
|
#include <Logging.h>
|
|
#include <XmlParserUtils.h>
|
|
|
|
#include <cstring>
|
|
|
|
OpdsParser::OpdsParser() {
|
|
parser = XML_ParserCreate(nullptr);
|
|
if (!parser) {
|
|
errorOccured = true;
|
|
LOG_DBG("OPDS", "Couldn't allocate memory for parser");
|
|
}
|
|
}
|
|
|
|
OpdsParser::~OpdsParser() { destroyXmlParser(parser); }
|
|
|
|
size_t OpdsParser::write(uint8_t c) { return write(&c, 1); }
|
|
|
|
size_t OpdsParser::write(const uint8_t* xmlData, const size_t length) {
|
|
if (errorOccured) return length;
|
|
|
|
XML_SetUserData(parser, this);
|
|
XML_SetElementHandler(parser, startElement, endElement);
|
|
XML_SetCharacterDataHandler(parser, characterData);
|
|
|
|
const char* currentPos = reinterpret_cast<const char*>(xmlData);
|
|
size_t remaining = length;
|
|
constexpr size_t chunkSize = 1024;
|
|
|
|
while (remaining > 0) {
|
|
void* const buf = XML_GetBuffer(parser, chunkSize);
|
|
if (!buf) {
|
|
errorOccured = true;
|
|
LOG_DBG("OPDS", "Couldn't allocate memory for buffer");
|
|
destroyXmlParser(parser);
|
|
return length;
|
|
}
|
|
|
|
const size_t toRead = remaining < chunkSize ? remaining : chunkSize;
|
|
memcpy(buf, currentPos, toRead);
|
|
|
|
if (XML_ParseBuffer(parser, static_cast<int>(toRead), 0) == XML_STATUS_ERROR) {
|
|
errorOccured = true;
|
|
LOG_DBG("OPDS", "Parse error at line %lu: %s", XML_GetCurrentLineNumber(parser),
|
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
|
destroyXmlParser(parser);
|
|
return length;
|
|
}
|
|
currentPos += toRead;
|
|
remaining -= toRead;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
void OpdsParser::flush() {
|
|
if (XML_Parse(parser, nullptr, 0, XML_TRUE) != XML_STATUS_OK) {
|
|
errorOccured = true;
|
|
destroyXmlParser(parser);
|
|
}
|
|
}
|
|
|
|
bool OpdsParser::error() const { return errorOccured; }
|
|
|
|
void OpdsParser::clear() {
|
|
entries.clear();
|
|
searchTemplate.clear();
|
|
nextPageUrl.clear();
|
|
prevPageUrl.clear();
|
|
currentEntry = OpdsEntry{};
|
|
currentText.clear();
|
|
inEntry = inTitle = inAuthor = inAuthorName = inId = false;
|
|
}
|
|
|
|
std::vector<OpdsEntry> OpdsParser::getBooks() const {
|
|
std::vector<OpdsEntry> books;
|
|
for (const auto& entry : entries) {
|
|
if (entry.type == OpdsEntryType::BOOK) books.push_back(entry);
|
|
}
|
|
return books;
|
|
}
|
|
|
|
const char* OpdsParser::findAttribute(const XML_Char** atts, const char* name) {
|
|
for (int i = 0; atts[i]; i += 2) {
|
|
if (strcmp(atts[i], name) == 0) return atts[i + 1];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
|
auto* self = static_cast<OpdsParser*>(userData);
|
|
|
|
if (strcmp(name, "link") == 0 || strstr(name, ":link") != nullptr) {
|
|
const char* href = findAttribute(atts, "href");
|
|
if (href) {
|
|
const char* rel = findAttribute(atts, "rel");
|
|
const char* type = findAttribute(atts, "type");
|
|
|
|
if (rel && strcmp(rel, "search") == 0) {
|
|
std::string sHref(href);
|
|
if (sHref.find("{searchTerms}") != std::string::npos) {
|
|
self->searchTemplate = sHref;
|
|
}
|
|
} else if (rel && strcmp(rel, "next") == 0 && !self->inEntry) {
|
|
self->nextPageUrl = href;
|
|
} else if (rel && strcmp(rel, "previous") == 0 && !self->inEntry) {
|
|
self->prevPageUrl = href;
|
|
}
|
|
|
|
if (self->inEntry) {
|
|
if (rel && type && strstr(rel, "opds-spec.org/acquisition") != nullptr &&
|
|
strcmp(type, "application/epub+zip") == 0) {
|
|
self->currentEntry.type = OpdsEntryType::BOOK;
|
|
self->currentEntry.href = href;
|
|
} else if (type && strstr(type, "application/atom+xml") != nullptr) {
|
|
if (self->currentEntry.type != OpdsEntryType::BOOK) {
|
|
self->currentEntry.type = OpdsEntryType::NAVIGATION;
|
|
self->currentEntry.href = href;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
|
|
self->inEntry = true;
|
|
self->currentEntry = OpdsEntry{};
|
|
return;
|
|
}
|
|
|
|
if (!self->inEntry) return;
|
|
|
|
if (strcmp(name, "title") == 0 || strstr(name, ":title") != nullptr) {
|
|
self->inTitle = true;
|
|
self->currentText.clear();
|
|
} else if (strcmp(name, "author") == 0 || strstr(name, ":author") != nullptr) {
|
|
self->inAuthor = true;
|
|
} else if (self->inAuthor && (strcmp(name, "name") == 0 || strstr(name, ":name") != nullptr)) {
|
|
self->inAuthorName = true;
|
|
self->currentText.clear();
|
|
} else if (strcmp(name, "id") == 0 || strstr(name, ":id") != nullptr) {
|
|
self->inId = true;
|
|
self->currentText.clear();
|
|
}
|
|
}
|
|
|
|
void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
|
|
auto* self = static_cast<OpdsParser*>(userData);
|
|
|
|
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
|
|
if (!self->currentEntry.title.empty() && !self->currentEntry.href.empty()) {
|
|
self->entries.push_back(self->currentEntry);
|
|
}
|
|
self->inEntry = false;
|
|
} else if (self->inEntry) {
|
|
if (strcmp(name, "title") == 0 || strstr(name, ":title") != nullptr) {
|
|
if (self->inTitle) self->currentEntry.title = self->currentText;
|
|
self->inTitle = false;
|
|
} else if (strcmp(name, "author") == 0 || strstr(name, ":author") != nullptr) {
|
|
self->inAuthor = false;
|
|
} else if (self->inAuthorName && (strcmp(name, "name") == 0 || strstr(name, ":name") != nullptr)) {
|
|
self->currentEntry.author = self->currentText;
|
|
self->inAuthorName = false;
|
|
} else if (strcmp(name, "id") == 0 || strstr(name, ":id") != nullptr) {
|
|
if (self->inId) self->currentEntry.id = self->currentText;
|
|
self->inId = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLCALL OpdsParser::characterData(void* userData, const XML_Char* s, const int len) {
|
|
auto* self = static_cast<OpdsParser*>(userData);
|
|
if (self->inTitle || self->inAuthorName || self->inId) {
|
|
self->currentText.append(s, len);
|
|
}
|
|
}
|