lets you navigate and download books now
This commit is contained in:
@@ -60,13 +60,13 @@ bool OpdsParser::parse(const char* xmlData, const size_t length) {
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
|
||||
Serial.printf("[%lu] [OPDS] Parsed %zu books\n", millis(), books.size());
|
||||
Serial.printf("[%lu] [OPDS] Parsed %zu entries\n", millis(), entries.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpdsParser::clear() {
|
||||
books.clear();
|
||||
currentBook = OpdsBook{};
|
||||
entries.clear();
|
||||
currentEntry = OpdsEntry{};
|
||||
currentText.clear();
|
||||
inEntry = false;
|
||||
inTitle = false;
|
||||
@@ -75,6 +75,16 @@ void OpdsParser::clear() {
|
||||
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) {
|
||||
@@ -90,7 +100,7 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons
|
||||
// Check for entry element (with or without namespace prefix)
|
||||
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
|
||||
self->inEntry = true;
|
||||
self->currentBook = OpdsBook{};
|
||||
self->currentEntry = OpdsEntry{};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,16 +133,26 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for link element with acquisition rel and epub type
|
||||
// Check for link element
|
||||
if (strcmp(name, "link") == 0 || strstr(name, ":link") != nullptr) {
|
||||
const char* rel = findAttribute(atts, "rel");
|
||||
const char* type = findAttribute(atts, "type");
|
||||
const char* href = findAttribute(atts, "href");
|
||||
|
||||
// Look for acquisition link with epub type
|
||||
if (rel && type && href) {
|
||||
if (strstr(rel, "opds-spec.org/acquisition") != nullptr && strcmp(type, "application/epub+zip") == 0) {
|
||||
self->currentBook.epubUrl = href;
|
||||
if (href) {
|
||||
// Check for acquisition link with epub type (this is a downloadable book)
|
||||
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;
|
||||
}
|
||||
// Check for navigation link (subsection or no rel specified with atom+xml type)
|
||||
else if (type && strstr(type, "application/atom+xml") != nullptr) {
|
||||
// Only set navigation link if we don't already have an epub link
|
||||
if (self->currentEntry.type != OpdsEntryType::BOOK) {
|
||||
self->currentEntry.type = OpdsEntryType::NAVIGATION;
|
||||
self->currentEntry.href = href;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,12 +163,12 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
|
||||
|
||||
// Check for entry end
|
||||
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
|
||||
// Only add book if it has required fields
|
||||
if (!self->currentBook.title.empty() && !self->currentBook.epubUrl.empty()) {
|
||||
self->books.push_back(self->currentBook);
|
||||
// Only add entry if it has required fields (title and href)
|
||||
if (!self->currentEntry.title.empty() && !self->currentEntry.href.empty()) {
|
||||
self->entries.push_back(self->currentEntry);
|
||||
}
|
||||
self->inEntry = false;
|
||||
self->currentBook = OpdsBook{};
|
||||
self->currentEntry = OpdsEntry{};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,7 +177,7 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
|
||||
// Check for title end
|
||||
if (strcmp(name, "title") == 0 || strstr(name, ":title") != nullptr) {
|
||||
if (self->inTitle) {
|
||||
self->currentBook.title = self->currentText;
|
||||
self->currentEntry.title = self->currentText;
|
||||
}
|
||||
self->inTitle = false;
|
||||
return;
|
||||
@@ -172,7 +192,7 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
|
||||
// Check for author name end
|
||||
if (self->inAuthor && (strcmp(name, "name") == 0 || strstr(name, ":name") != nullptr)) {
|
||||
if (self->inAuthorName) {
|
||||
self->currentBook.author = self->currentText;
|
||||
self->currentEntry.author = self->currentText;
|
||||
}
|
||||
self->inAuthorName = false;
|
||||
return;
|
||||
@@ -181,7 +201,7 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
|
||||
// Check for id end
|
||||
if (strcmp(name, "id") == 0 || strstr(name, ":id") != nullptr) {
|
||||
if (self->inId) {
|
||||
self->currentBook.id = self->currentText;
|
||||
self->currentEntry.id = self->currentText;
|
||||
}
|
||||
self->inId = false;
|
||||
return;
|
||||
|
||||
@@ -5,15 +5,27 @@
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Represents a book entry from an OPDS feed.
|
||||
* Type of OPDS entry.
|
||||
*/
|
||||
struct OpdsBook {
|
||||
enum class OpdsEntryType {
|
||||
NAVIGATION, // Link to another catalog
|
||||
BOOK // Downloadable book
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an entry from an OPDS feed (either a navigation link or a book).
|
||||
*/
|
||||
struct OpdsEntry {
|
||||
OpdsEntryType type = OpdsEntryType::NAVIGATION;
|
||||
std::string title;
|
||||
std::string author;
|
||||
std::string epubUrl; // Relative URL like "/books/get/epub/3/Calibre_Library"
|
||||
std::string author; // Only for books
|
||||
std::string href; // Navigation URL or epub download URL
|
||||
std::string id;
|
||||
};
|
||||
|
||||
// Legacy alias for backward compatibility
|
||||
using OpdsBook = OpdsEntry;
|
||||
|
||||
/**
|
||||
* Parser for OPDS (Open Publication Distribution System) Atom feeds.
|
||||
* Uses the Expat XML parser to parse OPDS catalog entries.
|
||||
@@ -21,8 +33,12 @@ struct OpdsBook {
|
||||
* Usage:
|
||||
* OpdsParser parser;
|
||||
* if (parser.parse(xmlData, xmlLength)) {
|
||||
* for (const auto& book : parser.getBooks()) {
|
||||
* // Process book entries
|
||||
* for (const auto& entry : parser.getEntries()) {
|
||||
* if (entry.type == OpdsEntryType::BOOK) {
|
||||
* // Downloadable book
|
||||
* } else {
|
||||
* // Navigation link to another catalog
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@@ -44,13 +60,19 @@ class OpdsParser {
|
||||
bool parse(const char* xmlData, size_t length);
|
||||
|
||||
/**
|
||||
* Get the parsed books.
|
||||
* @return Vector of OpdsBook entries
|
||||
* Get the parsed entries (both navigation and book entries).
|
||||
* @return Vector of OpdsEntry entries
|
||||
*/
|
||||
const std::vector<OpdsBook>& getBooks() const { return books; }
|
||||
const std::vector<OpdsEntry>& getEntries() const { return entries; }
|
||||
|
||||
/**
|
||||
* Clear all parsed books.
|
||||
* Get only book entries (legacy compatibility).
|
||||
* @return Vector of book entries
|
||||
*/
|
||||
std::vector<OpdsEntry> getBooks() const;
|
||||
|
||||
/**
|
||||
* Clear all parsed entries.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
@@ -64,8 +86,8 @@ class OpdsParser {
|
||||
static const char* findAttribute(const XML_Char** atts, const char* name);
|
||||
|
||||
XML_Parser parser = nullptr;
|
||||
std::vector<OpdsBook> books;
|
||||
OpdsBook currentBook;
|
||||
std::vector<OpdsEntry> entries;
|
||||
OpdsEntry currentEntry;
|
||||
std::string currentText;
|
||||
|
||||
// Parser state
|
||||
|
||||
Reference in New Issue
Block a user