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
@@ -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);