feat: implement CSS enumeration through OPF directory when missing from manifest (#2148)
This commit is contained in:
+40
-1
@@ -255,6 +255,38 @@ bool Epub::parseTocNavFile() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Epub::discoverCssFilesFromZip() {
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
LOG_ERR("EBP", "Cannot discover CSS from ZIP because book metadata cache is not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
ZipFile zf(filepath);
|
||||
|
||||
if (!zf.loadAllFileStatSlims()) {
|
||||
LOG_ERR("EBP", "Failed to load ZIP file stat slims for CSS discovery");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t lastSlash = contentBasePath.find_last_of('/');
|
||||
|
||||
std::string opfDir = (lastSlash != std::string::npos) ? contentBasePath.substr(0, lastSlash + 1) : "";
|
||||
|
||||
zf.enumerateFilePaths([&](std::string_view filePath) {
|
||||
if (!opfDir.empty() && filePath.find(opfDir) != 0) {
|
||||
return; // Skip files that are not in the same directory as OPF manifest, as CSS files are typically located
|
||||
// there or in subfolders
|
||||
}
|
||||
|
||||
if (FsHelpers::hasCssExtension(filePath)) {
|
||||
if (std::find(cssFiles.begin(), cssFiles.end(), filePath) == cssFiles.end()) {
|
||||
LOG_DBG("EBP", "Discovered CSS file via ZIP enumeration: %.*s", (int)filePath.size(), filePath.data());
|
||||
cssFiles.push_back(std::string{filePath});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Epub::parseCssFiles() const {
|
||||
// Maximum CSS file size we'll attempt to parse (uncompressed)
|
||||
// Larger files risk memory exhaustion on ESP32
|
||||
@@ -329,9 +361,9 @@ void Epub::parseCssFiles() const {
|
||||
if (!cssParser->saveToCache()) {
|
||||
LOG_ERR("EBP", "Failed to save CSS rules to cache");
|
||||
}
|
||||
cssParser->clear();
|
||||
|
||||
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
|
||||
cssParser->clear();
|
||||
}
|
||||
|
||||
// load in the meta data for the epub file
|
||||
@@ -354,6 +386,10 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
||||
if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
|
||||
LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files");
|
||||
// continue anyway - book will work without CSS and we'll still load any inline style CSS
|
||||
} else {
|
||||
// Handle case where CSS files are not listed in OPF manifest
|
||||
// but are still referenced by HTML files - discover and parse them too
|
||||
discoverCssFilesFromZip();
|
||||
}
|
||||
parseCssFiles();
|
||||
// Invalidate section caches so they are rebuilt with the new CSS
|
||||
@@ -457,6 +493,9 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
||||
}
|
||||
|
||||
if (!skipLoadingCss) {
|
||||
// Handle case where CSS files are not listed in OPF manifest
|
||||
// but are still referenced by HTML files - discover and parse them too
|
||||
discoverCssFilesFromZip();
|
||||
// Parse CSS files after cache reload
|
||||
parseCssFiles();
|
||||
Storage.removeDir((cachePath + "/sections").c_str());
|
||||
|
||||
@@ -35,6 +35,7 @@ class Epub {
|
||||
bool parseTocNcxFile() const;
|
||||
bool parseTocNavFile() const;
|
||||
void parseCssFiles() const;
|
||||
void discoverCssFilesFromZip();
|
||||
|
||||
public:
|
||||
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
|
||||
|
||||
@@ -129,6 +129,8 @@ bool hasTxtExtension(std::string_view fileName) { return checkFileExtension(file
|
||||
|
||||
bool hasMarkdownExtension(std::string_view fileName) { return checkFileExtension(fileName, ".md"); }
|
||||
|
||||
bool hasCssExtension(std::string_view fileName) { return checkFileExtension(fileName, ".css"); }
|
||||
|
||||
std::string extractFolderPath(const std::string& filePath) {
|
||||
const auto lastSlash = filePath.find_last_of('/');
|
||||
if (lastSlash == std::string::npos || lastSlash == 0) {
|
||||
|
||||
@@ -58,6 +58,11 @@ inline bool hasTxtExtension(const String& fileName) {
|
||||
// Check for .md extension (case-insensitive)
|
||||
bool hasMarkdownExtension(std::string_view fileName);
|
||||
|
||||
// Check for .css extension (case-insensitive)
|
||||
bool hasCssExtension(std::string_view fileName);
|
||||
inline bool hasCssExtension(const String& fileName) {
|
||||
return hasCssExtension(std::string_view{fileName.c_str(), fileName.length()});
|
||||
}
|
||||
std::string extractFolderPath(const std::string& filePath);
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
class ZipFile {
|
||||
@@ -69,4 +70,11 @@ class ZipFile {
|
||||
// These functions will open and close the zip as needed
|
||||
uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false);
|
||||
bool readFileToStream(const char* filename, Print& out, size_t chunkSize);
|
||||
|
||||
template <typename F>
|
||||
void enumerateFilePaths(F&& callback) const {
|
||||
for (const auto& entry : fileStatSlimCache) {
|
||||
callback(std::string_view{entry.first});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user