Add DYNAMIC_DATA placeholder for raw streamed template content

Support registering dynamic String/progmem data without re-parsing as
nested templates, used by WiFiManager for composed portal stylesheets.
This commit is contained in:
2026-05-04 22:03:42 +10:00
parent e3af7eacbf
commit 2a1ef2de63
4 changed files with 96 additions and 1 deletions
@@ -90,6 +90,7 @@ public:
* @return true if registered successfully
*/
bool registerRamData(const char* name, PlaceholderDataGetter getter);
bool registerDynamicData(const char* name, const DynamicDataDescriptor* descriptor);
bool registerDynamicTemplate(const char* name, const DynamicTemplateDescriptor* descriptor);
bool registerConditional(const char* name, const ConditionalDescriptor* descriptor);
bool registerIterator(const char* name, const IteratorDescriptor* descriptor);
@@ -124,6 +125,7 @@ public:
// Static helper functions for length calculation
static size_t getProgmemLength(const void* data);
static size_t getRamLength(const void* data);
static size_t getDynamicDataLength(const DynamicDataDescriptor* descriptor, const char* data);
static size_t getDynamicTemplateLength(const DynamicTemplateDescriptor* descriptor, const char* templateData);
private:
@@ -138,6 +140,8 @@ private:
uint8_t* dest, size_t maxLen);
static size_t copyRamData(PlaceholderDataGetter getter, size_t offset,
uint8_t* dest, size_t maxLen);
static size_t copyDynamicData(const DynamicDataDescriptor* descriptor, size_t offset,
uint8_t* dest, size_t maxLen);
};
#endif // DEVICEFRAMEWORK_PLACEHOLDER_REGISTRY_H
+7
View File
@@ -31,6 +31,7 @@ enum class PlaceholderType {
PROGMEM_DATA, // Large PROGMEM data (CSS, JS, base64 images)
PROGMEM_TEMPLATE, // Nested template in PROGMEM
RAM_DATA, // Dynamic RAM data via getter functions
DYNAMIC_DATA, // Dynamic RAM data via getter + userData, rendered as raw bytes
DYNAMIC_TEMPLATE,
CONDITIONAL,
ITERATOR
@@ -64,6 +65,12 @@ typedef void* (*IteratorOpenHandler)(void* userData);
typedef IteratorStepResult (*IteratorNextHandler)(void* handle, struct IteratorItemView& view);
typedef void (*IteratorCloseHandler)(void* handle);
struct DynamicDataDescriptor {
DynamicTemplateGetter getter;
DynamicTemplateLengthGetter getLength;
void* userData;
};
struct DynamicTemplateDescriptor {
DynamicTemplateGetter getter;
DynamicTemplateLengthGetter getLength;
@@ -125,6 +125,39 @@ bool DeviceFrameworkPlaceholderRegistry::registerRamData(const char* name, Place
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerDynamicData(const char* name, const DynamicDataDescriptor* descriptor) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
return false;
}
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
}
if (!validatePlaceholderName(name)) {
return false;
}
if (descriptor == nullptr || descriptor->getter == nullptr) {
DFTE_LOG_ERROR("Invalid dynamic data descriptor for placeholder: " + String(name));
return false;
}
PlaceholderEntry& entry = placeholders[count];
strncpy(entry.name, name, sizeof(entry.name) - 1);
entry.name[sizeof(entry.name) - 1] = '\0';
entry.type = PlaceholderType::DYNAMIC_DATA;
entry.data = descriptor;
entry.getLength = nullptr;
entry.cachedLength = 0;
entry.hasCachedLength = false;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerDynamicTemplate(const char* name, const DynamicTemplateDescriptor* descriptor) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
@@ -296,6 +329,9 @@ size_t DeviceFrameworkPlaceholderRegistry::renderPlaceholder(const PlaceholderEn
case PlaceholderType::RAM_DATA:
return copyRamData((PlaceholderDataGetter)entry->data, offset, buffer, maxLen);
case PlaceholderType::DYNAMIC_DATA:
return copyDynamicData(static_cast<const DynamicDataDescriptor*>(entry->data), offset, buffer, maxLen);
case PlaceholderType::DYNAMIC_TEMPLATE:
case PlaceholderType::CONDITIONAL:
case PlaceholderType::ITERATOR:
@@ -323,6 +359,18 @@ size_t DeviceFrameworkPlaceholderRegistry::getRamLength(const void* data) {
return strlen(str);
}
size_t DeviceFrameworkPlaceholderRegistry::getDynamicDataLength(const DynamicDataDescriptor* descriptor, const char* data) {
if (descriptor == nullptr || data == nullptr) {
return 0;
}
if (descriptor->getLength) {
return descriptor->getLength(data, descriptor->userData);
}
return strlen(data);
}
size_t DeviceFrameworkPlaceholderRegistry::getDynamicTemplateLength(const DynamicTemplateDescriptor* descriptor, const char* templateData) {
if (descriptor == nullptr || templateData == nullptr) {
return 0;
@@ -370,3 +418,25 @@ size_t DeviceFrameworkPlaceholderRegistry::copyRamData(PlaceholderDataGetter get
return chunkSize;
}
size_t DeviceFrameworkPlaceholderRegistry::copyDynamicData(const DynamicDataDescriptor* descriptor,
size_t offset,
uint8_t* dest,
size_t maxLen) {
if (descriptor == nullptr || descriptor->getter == nullptr || maxLen == 0) return 0;
const char* data = descriptor->getter(descriptor->userData);
if (data == nullptr) {
return 0;
}
size_t dataLen = getDynamicDataLength(descriptor, data);
if (offset >= dataLen) return 0;
size_t remaining = dataLen - offset;
constexpr size_t MAX_CHUNK = DFTE_RAM_CHUNK_SIZE;
size_t chunkSize = min(min(maxLen, remaining), MAX_CHUNK);
memcpy(dest, data + offset, chunkSize);
return chunkSize;
}
+15 -1
View File
@@ -24,6 +24,15 @@ static bool pushPlaceholderEntry(DeviceFrameworkTemplateContext& ctx, const Plac
dataCtx->context.data.offset = 0;
return true;
}
case PlaceholderType::DYNAMIC_DATA: {
if (!ctx.pushContext(RenderingContextType::PLACEHOLDER_DATA, name)) {
return false;
}
RenderingContext* dataCtx = ctx.getCurrentContext();
dataCtx->context.data.entry = entry;
dataCtx->context.data.offset = 0;
return true;
}
case PlaceholderType::PROGMEM_TEMPLATE: {
if (!ctx.pushContext(RenderingContextType::PLACEHOLDER_TEMPLATE, name)) {
return false;
@@ -659,6 +668,7 @@ DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::
switch (entry->type) {
case PlaceholderType::PROGMEM_DATA:
case PlaceholderType::RAM_DATA:
case PlaceholderType::DYNAMIC_DATA:
outcome.pushContext.type = RenderingContextType::PLACEHOLDER_DATA;
outcome.pushContext.entry = entry;
break;
@@ -768,7 +778,11 @@ DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::
}
size_t totalLength = 0;
if (entry->hasCachedLength) {
if (entry->type == PlaceholderType::DYNAMIC_DATA) {
const auto* descriptor = static_cast<const DynamicDataDescriptor*>(entry->data);
const char* data = (descriptor && descriptor->getter) ? descriptor->getter(descriptor->userData) : nullptr;
totalLength = DeviceFrameworkPlaceholderRegistry::getDynamicDataLength(descriptor, data);
} else if (entry->hasCachedLength) {
totalLength = entry->cachedLength;
} else if (entry->getLength != nullptr) {
totalLength = entry->getLength(entry->data);