Add DFTE sources and tests

This commit is contained in:
2025-11-09 10:17:57 +10:00
parent 470efbf2d4
commit 5719aa6c39
27 changed files with 4993 additions and 0 deletions
+294
View File
@@ -0,0 +1,294 @@
#include "DeviceFrameworkPlaceholderRegistry.h"
#include "DeviceFrameworkTemplateEngineDebug.h"
#include <pgmspace.h>
DeviceFrameworkPlaceholderRegistry::DeviceFrameworkPlaceholderRegistry(uint16_t maxPlaceholders)
: maxPlaceholders(maxPlaceholders), count(0) {
placeholders = new PlaceholderEntry[maxPlaceholders];
memset(placeholders, 0, sizeof(PlaceholderEntry) * maxPlaceholders);
}
DeviceFrameworkPlaceholderRegistry::~DeviceFrameworkPlaceholderRegistry() {
if (placeholders) {
delete[] placeholders;
placeholders = nullptr;
}
}
bool DeviceFrameworkPlaceholderRegistry::registerProgmemData(const char* name, const char* progmemData) {
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
}
if (!validatePlaceholderName(name)) {
return false;
}
// Check for duplicates
if (getPlaceholder(name) != nullptr) {
DFTE_LOG_WARN("Placeholder already registered: " + String(name));
// Continue anyway - last registration wins
}
PlaceholderEntry& entry = placeholders[count];
strncpy(entry.name, name, sizeof(entry.name) - 1);
entry.name[sizeof(entry.name) - 1] = '\0';
entry.type = PlaceholderType::PROGMEM_DATA;
entry.data = progmemData;
entry.getLength = getProgmemLength;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerProgmemTemplate(const char* name, const char* progmemTemplate) {
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
}
if (!validatePlaceholderName(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::PROGMEM_TEMPLATE;
entry.data = progmemTemplate;
entry.getLength = getProgmemLength;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerRamData(const char* name, PlaceholderDataGetter getter) {
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
}
if (!validatePlaceholderName(name)) {
return false;
}
if (getter == nullptr) {
DFTE_LOG_ERROR("Cannot register RAM_DATA placeholder with null getter: " + 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::RAM_DATA;
entry.data = (const void*)getter;
entry.getLength = getRamLength;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerDynamicTemplate(const char* name, const DynamicTemplateDescriptor* descriptor) {
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 template 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_TEMPLATE;
entry.data = descriptor;
entry.getLength = nullptr;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerConditional(const char* name, const ConditionalDescriptor* descriptor) {
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
}
if (!validatePlaceholderName(name)) {
return false;
}
if (descriptor == nullptr || descriptor->evaluate == nullptr) {
DFTE_LOG_ERROR("Invalid conditional 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::CONDITIONAL;
entry.data = descriptor;
entry.getLength = nullptr;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerIterator(const char* name, const IteratorDescriptor* descriptor) {
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
}
if (!validatePlaceholderName(name)) {
return false;
}
if (descriptor == nullptr || descriptor->next == nullptr) {
DFTE_LOG_ERROR("Invalid iterator 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::ITERATOR;
entry.data = descriptor;
entry.getLength = nullptr;
count++;
return true;
}
void DeviceFrameworkPlaceholderRegistry::clear() {
count = 0;
memset(placeholders, 0, sizeof(PlaceholderEntry) * maxPlaceholders);
}
bool DeviceFrameworkPlaceholderRegistry::validatePlaceholderName(const char* name) const {
if (!name) {
DFTE_LOG_ERROR("Placeholder name is null");
return false;
}
size_t nameLen = strlen(name);
if (nameLen >= MAX_PLACEHOLDER_NAME_SIZE) {
DFTE_LOG_ERROR("Placeholder name too long: " + String(name) + " (max: " + String(MAX_PLACEHOLDER_NAME_SIZE) + ")");
return false;
}
if (nameLen >= sizeof(PlaceholderEntry::name)) {
// This shouldn't happen if MAX_PLACEHOLDER_NAME_SIZE is <= sizeof(PlaceholderEntry::name)
DFTE_LOG_ERROR("Placeholder name exceeds entry buffer size");
return false;
}
return true;
}
const PlaceholderEntry* DeviceFrameworkPlaceholderRegistry::getPlaceholder(const char* name) const {
if (name == nullptr) return nullptr;
for (int i = count - 1; i >= 0; i--) {
if (strcmp(placeholders[i].name, name) == 0) {
return &placeholders[i];
}
}
return nullptr;
}
size_t DeviceFrameworkPlaceholderRegistry::renderPlaceholder(const PlaceholderEntry* entry, size_t offset,
uint8_t* buffer, size_t maxLen) const {
if (entry == nullptr || maxLen == 0) {
return 0;
}
switch (entry->type) {
case PlaceholderType::PROGMEM_DATA:
case PlaceholderType::PROGMEM_TEMPLATE:
return copyProgmemData((const char*)entry->data, offset, buffer, maxLen);
case PlaceholderType::RAM_DATA:
return copyRamData((PlaceholderDataGetter)entry->data, offset, buffer, maxLen);
case PlaceholderType::DYNAMIC_TEMPLATE:
case PlaceholderType::CONDITIONAL:
case PlaceholderType::ITERATOR:
// Dynamic template, conditional, and iterator content are handled directly by the renderer
return 0;
default:
return 0;
}
}
size_t DeviceFrameworkPlaceholderRegistry::getProgmemLength(const void* data) {
if (data == nullptr) return 0;
return strlen_P((const char*)data);
}
size_t DeviceFrameworkPlaceholderRegistry::getRamLength(const void* data) {
if (data == nullptr) return 0;
PlaceholderDataGetter getter = (PlaceholderDataGetter)data;
const char* str = getter();
if (str == nullptr) {
return 0;
}
return strlen(str);
}
size_t DeviceFrameworkPlaceholderRegistry::getDynamicTemplateLength(const DynamicTemplateDescriptor* descriptor, const char* templateData) {
if (descriptor == nullptr || templateData == nullptr) {
return 0;
}
if (descriptor->getLength) {
return descriptor->getLength(templateData, descriptor->userData);
}
return strlen(templateData);
}
size_t DeviceFrameworkPlaceholderRegistry::copyProgmemData(const char* source, size_t offset, uint8_t* dest, size_t maxLen) {
if (source == nullptr || maxLen == 0) return 0;
size_t dataLen = strlen_P(source);
if (offset >= dataLen) return 0;
size_t remaining = dataLen - offset;
// Use configurable chunk size - matches CONFIG_templateProgmemChunkSize when DeviceFramework is present
constexpr size_t MAX_CHUNK = DFTE_PROGMEM_CHUNK_SIZE;
size_t chunkSize = min(min(maxLen, remaining), MAX_CHUNK);
memcpy_P(dest, source + offset, chunkSize);
return chunkSize;
}
size_t DeviceFrameworkPlaceholderRegistry::copyRamData(PlaceholderDataGetter getter, size_t offset, uint8_t* dest, size_t maxLen) {
if (getter == nullptr || maxLen == 0) return 0;
const char* data = getter();
if (data == nullptr) {
return 0;
}
size_t dataLen = strlen(data);
if (offset >= dataLen) return 0;
size_t remaining = dataLen - offset;
// Use configurable chunk size - matches CONFIG_templateRamChunkSize when DeviceFramework is present
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;
}
+260
View File
@@ -0,0 +1,260 @@
#include "DeviceFrameworkTemplateContext.h"
#include "DeviceFrameworkTemplateEngineDebug.h"
DeviceFrameworkTemplateContext::DeviceFrameworkTemplateContext()
: state(TemplateRenderState::TEXT), renderingDepth(0), placeholderPos(0),
bufferPos(0), bufferLen(0), bufferOffset(0),
registry(nullptr),
totalBytesProcessed(0), startTime(0) {
memset(placeholderName, 0, sizeof(placeholderName));
memset(renderingStack, 0, sizeof(renderingStack));
}
void DeviceFrameworkTemplateContext::reset() {
state = TemplateRenderState::TEXT;
renderingDepth = 0;
placeholderPos = 0;
bufferPos = 0;
bufferLen = 0;
bufferOffset = 0;
totalBytesProcessed = 0;
startTime = millis();
memset(placeholderName, 0, sizeof(placeholderName));
memset(renderingStack, 0, sizeof(renderingStack));
}
// Unified stack management methods
bool DeviceFrameworkTemplateContext::pushContext(RenderingContextType type, const char* name) {
if (renderingDepth >= MAX_RENDERING_DEPTH) {
DFTE_LOG_ERROR("Rendering stack overflow! Depth=" + String(renderingDepth));
state = TemplateRenderState::ERROR;
return false;
}
// Save current buffer state if we're currently in a template context
if (renderingDepth > 0) {
RenderingContext* currentCtx = getCurrentContext();
if (currentCtx && currentCtx->type == RenderingContextType::TEMPLATE) {
// Save buffer state to current template context
currentCtx->context.templateCtx.bufferPos = bufferPos;
currentCtx->context.templateCtx.bufferLen = bufferLen;
currentCtx->context.templateCtx.bufferOffset = bufferOffset;
}
}
RenderingContext& ctx = renderingStack[renderingDepth];
ctx.type = type;
ctx.name = name;
// Initialize buffer state for new template context
if (type == RenderingContextType::TEMPLATE) {
ctx.context.templateCtx.bufferPos = 0;
ctx.context.templateCtx.bufferLen = 0;
ctx.context.templateCtx.bufferOffset = 0;
ctx.context.templateCtx.iteratorPlaceholders = nullptr;
ctx.context.templateCtx.iteratorPlaceholderCount = 0;
bufferPos = 0;
bufferLen = 0;
bufferOffset = 0;
}
renderingDepth++;
return true;
}
void DeviceFrameworkTemplateContext::popContext() {
if (renderingDepth <= 0) {
DFTE_LOG_ERROR("Rendering stack underflow!");
state = TemplateRenderState::ERROR;
return;
}
renderingDepth--;
RenderingContext& ctx = renderingStack[renderingDepth];
if (ctx.type == RenderingContextType::PLACEHOLDER_ITERATOR) {
const IteratorDescriptor* descriptor = ctx.context.iterator.descriptor;
if (descriptor && descriptor->close && ctx.context.iterator.handleOpen) {
descriptor->close(ctx.context.iterator.handle);
}
}
// Restore buffer state from parent template context if it exists
if (renderingDepth > 0) {
RenderingContext* parentCtx = getCurrentContext();
if (parentCtx && parentCtx->type == RenderingContextType::TEMPLATE) {
auto& parentTemplateCtx = parentCtx->context.templateCtx;
// Invalidate shared buffer so parent refill starts fresh
bufferPos = 0;
bufferLen = 0;
bufferOffset = parentTemplateCtx.position;
// Clear cached buffer state stored on the parent template context
parentTemplateCtx.bufferPos = 0;
parentTemplateCtx.bufferLen = 0;
parentTemplateCtx.bufferOffset = parentTemplateCtx.position;
} else {
// No parent template context, reset buffer state
bufferPos = 0;
bufferLen = 0;
bufferOffset = 0;
}
} else {
// No more contexts, reset buffer state
bufferPos = 0;
bufferLen = 0;
bufferOffset = 0;
}
// Clear the popped context
memset(&ctx, 0, sizeof(RenderingContext));
}
RenderingContext* DeviceFrameworkTemplateContext::getCurrentContext() {
if (renderingDepth == 0) return nullptr;
return &renderingStack[renderingDepth - 1];
}
RenderingContext* DeviceFrameworkTemplateContext::getContext(int depth) {
if (depth < 0 || depth >= renderingDepth) return nullptr;
return &renderingStack[depth];
}
bool DeviceFrameworkTemplateContext::isRenderingTemplate() const {
if (renderingDepth == 0) return false;
return renderingStack[renderingDepth - 1].type == RenderingContextType::TEMPLATE;
}
bool DeviceFrameworkTemplateContext::isRenderingPlaceholder() const {
if (renderingDepth == 0) return false;
RenderingContextType type = renderingStack[renderingDepth - 1].type;
return type == RenderingContextType::PLACEHOLDER_DATA ||
type == RenderingContextType::PLACEHOLDER_TEMPLATE;
}
RenderingContextType DeviceFrameworkTemplateContext::getCurrentContextType() const {
if (renderingDepth == 0) return RenderingContextType::TEMPLATE; // Default
return renderingStack[renderingDepth - 1].type;
}
bool DeviceFrameworkTemplateContext::isComplete() const {
return state == TemplateRenderState::COMPLETE || state == TemplateRenderState::ERROR;
}
bool DeviceFrameworkTemplateContext::hasError() const {
return state == TemplateRenderState::ERROR;
}
String DeviceFrameworkTemplateContext::getStateString() const {
switch (state) {
case TemplateRenderState::TEXT: return "TEXT";
case TemplateRenderState::BUILDING_PLACEHOLDER: return "BUILDING_PLACEHOLDER";
case TemplateRenderState::RENDERING_CONTEXT: return "RENDERING_CONTEXT";
case TemplateRenderState::COMPLETE: return "COMPLETE";
case TemplateRenderState::ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
String DeviceFrameworkTemplateContext::getStackTrace() const {
String trace = "Stack trace (depth=" + String(renderingDepth) + "):\n";
for (int i = 0; i < renderingDepth; i++) {
const RenderingContext& ctx = renderingStack[i];
String typeStr;
switch (ctx.type) {
case RenderingContextType::TEMPLATE: typeStr = "TEMPLATE"; break;
case RenderingContextType::PLACEHOLDER_DATA: typeStr = "PLACEHOLDER_DATA"; break;
case RenderingContextType::PLACEHOLDER_TEMPLATE: typeStr = "PLACEHOLDER_TEMPLATE"; break;
default: typeStr = "UNKNOWN"; break;
}
trace += " [" + String(i) + "] " + String(ctx.name) + " (type=" + typeStr + ")";
if (ctx.type == RenderingContextType::TEMPLATE) {
trace += " at pos " + String(ctx.context.templateCtx.position);
} else if (ctx.type == RenderingContextType::PLACEHOLDER_DATA) {
trace += " at offset " + String(ctx.context.data.offset);
}
trace += "\n";
}
return trace;
}
bool DeviceFrameworkTemplateContext::refillBuffer() {
RenderingContext* currentCtx = getCurrentContext();
if (!currentCtx || currentCtx->type != RenderingContextType::TEMPLATE) {
return false;
}
auto& templateCtx = currentCtx->context.templateCtx;
if (templateCtx.position >= templateCtx.templateLen) {
return false;
}
bufferLen = min(BUFFER_SIZE, templateCtx.templateLen - templateCtx.position);
if (bufferLen > 0) {
if (templateCtx.isProgmem) {
memcpy_P(readBuffer, templateCtx.templateData + templateCtx.position, bufferLen);
} else {
memcpy(readBuffer, templateCtx.templateData + templateCtx.position, bufferLen);
}
bufferPos = 0;
bufferOffset = templateCtx.position;
// Save buffer state to template context
templateCtx.bufferPos = bufferPos;
templateCtx.bufferLen = bufferLen;
templateCtx.bufferOffset = bufferOffset;
// Don't advance templateCtx.position here - it will be updated as we read characters
return true;
}
return false;
}
char DeviceFrameworkTemplateContext::getNextChar() {
RenderingContext* currentCtx = getCurrentContext();
if (!currentCtx || currentCtx->type != RenderingContextType::TEMPLATE) {
return '\0';
}
// Restore buffer state from template context if needed
auto& templateCtx = currentCtx->context.templateCtx;
if (bufferPos == 0 && bufferLen == 0 && templateCtx.bufferLen > 0) {
// Buffer state might be stale, restore from context
bufferPos = templateCtx.bufferPos;
bufferLen = templateCtx.bufferLen;
bufferOffset = templateCtx.bufferOffset;
}
if (bufferPos >= bufferLen) {
if (!refillBuffer()) {
return '\0'; // End of template
}
}
char c = readBuffer[bufferPos++];
// Update template position to reflect current position
templateCtx.position = bufferOffset + bufferPos;
// Save buffer state to template context
templateCtx.bufferPos = bufferPos;
templateCtx.bufferLen = bufferLen;
templateCtx.bufferOffset = bufferOffset;
return c;
}
size_t DeviceFrameworkTemplateContext::getAvailableBytes() const {
return bufferLen - bufferPos;
}
bool DeviceFrameworkTemplateContext::hasMoreData() const {
RenderingContext* currentCtx = const_cast<DeviceFrameworkTemplateContext*>(this)->getCurrentContext();
if (!currentCtx || currentCtx->type != RenderingContextType::TEMPLATE) {
return false;
}
const auto& templateCtx = currentCtx->context.templateCtx;
return templateCtx.position < templateCtx.templateLen || bufferPos < bufferLen;
}
void DeviceFrameworkTemplateContext::resetPlaceholder() {
placeholderPos = 0;
memset(placeholderName, 0, sizeof(placeholderName));
}
@@ -0,0 +1,28 @@
#include "DeviceFrameworkTemplateEngineDebug.h"
// Global logger pointer - starts as nullptr (disabled by default)
DeviceFrameworkTemplateEngineLogger* deviceFrameworkTemplateEngineLogger = nullptr;
// ============================================================================
// LOGGING CONFIGURATION IMPLEMENTATIONS
// ============================================================================
bool deviceFrameworkTemplateEngineEnableLogging(DeviceFrameworkTemplateEngineLogger* logger) {
if (logger == nullptr) {
return false;
}
deviceFrameworkTemplateEngineLogger = logger;
return true;
}
void deviceFrameworkTemplateEngineDisableLogging() {
deviceFrameworkTemplateEngineLogger = nullptr;
}
bool deviceFrameworkTemplateEngineIsLoggingEnabled() {
return deviceFrameworkTemplateEngineLogger != nullptr;
}
DeviceFrameworkTemplateEngineLogger* deviceFrameworkTemplateEngineGetLogger() {
return deviceFrameworkTemplateEngineLogger;
}
+860
View File
@@ -0,0 +1,860 @@
#include "DeviceFrameworkTemplateRenderer.h"
#include "DeviceFrameworkTemplateEngineDebug.h"
#include <pgmspace.h>
#include <cstring>
namespace {
static bool pushPlaceholderEntry(DeviceFrameworkTemplateContext& ctx, const PlaceholderEntry* entry, const char* nameOverride = nullptr) {
if (entry == nullptr) {
DFTE_LOG_ERROR("Attempted to push null placeholder entry");
return false;
}
const char* name = nameOverride ? nameOverride : entry->name;
switch (entry->type) {
case PlaceholderType::PROGMEM_DATA:
case PlaceholderType::RAM_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;
}
RenderingContext* placeholderCtx = ctx.getCurrentContext();
placeholderCtx->context.templatePlaceholder.entry = entry;
if (!ctx.pushContext(RenderingContextType::TEMPLATE, name)) {
ctx.popContext();
return false;
}
RenderingContext* templateCtx = ctx.getCurrentContext();
templateCtx->context.templateCtx.templateData = static_cast<const char*>(entry->data);
templateCtx->context.templateCtx.templateLen = entry->getLength(entry->data);
templateCtx->context.templateCtx.isProgmem = true;
templateCtx->context.templateCtx.position = 0;
templateCtx->context.templateCtx.iteratorPlaceholders = nullptr;
templateCtx->context.templateCtx.iteratorPlaceholderCount = 0;
return true;
}
case PlaceholderType::DYNAMIC_TEMPLATE: {
if (!ctx.pushContext(RenderingContextType::PLACEHOLDER_DYNAMIC_TEMPLATE, name)) {
return false;
}
RenderingContext* dynamicCtx = ctx.getCurrentContext();
dynamicCtx->context.dynamicTemplate.entry = entry;
dynamicCtx->context.dynamicTemplate.offset = 0;
dynamicCtx->context.dynamicTemplate.templateData = nullptr;
dynamicCtx->context.dynamicTemplate.templateLength = 0;
const DynamicTemplateDescriptor* descriptor = static_cast<const DynamicTemplateDescriptor*>(entry->data);
if (descriptor == nullptr || descriptor->getter == nullptr) {
DFTE_LOG_ERROR("Dynamic template placeholder missing descriptor: " + String(name));
ctx.popContext();
return false;
}
const char* templateData = descriptor->getter(descriptor->userData);
if (templateData == nullptr) {
DFTE_LOG_WARN("Dynamic template getter returned null for placeholder: " + String(name));
templateData = "";
}
size_t templateLen = DeviceFrameworkPlaceholderRegistry::getDynamicTemplateLength(descriptor, templateData);
dynamicCtx->context.dynamicTemplate.templateData = templateData;
dynamicCtx->context.dynamicTemplate.templateLength = templateLen;
if (!ctx.pushContext(RenderingContextType::TEMPLATE, name)) {
ctx.popContext();
return false;
}
RenderingContext* templateCtx = ctx.getCurrentContext();
templateCtx->context.templateCtx.templateData = templateData;
templateCtx->context.templateCtx.templateLen = templateLen;
templateCtx->context.templateCtx.isProgmem = false;
templateCtx->context.templateCtx.position = 0;
templateCtx->context.templateCtx.iteratorPlaceholders = nullptr;
templateCtx->context.templateCtx.iteratorPlaceholderCount = 0;
return true;
}
case PlaceholderType::CONDITIONAL: {
const ConditionalDescriptor* descriptor = static_cast<const ConditionalDescriptor*>(entry->data);
if (descriptor == nullptr || descriptor->evaluate == nullptr) {
DFTE_LOG_ERROR("Conditional placeholder missing descriptor: " + String(name));
return false;
}
if (!ctx.pushContext(RenderingContextType::PLACEHOLDER_CONDITIONAL, name)) {
return false;
}
RenderingContext* conditionalCtx = ctx.getCurrentContext();
conditionalCtx->context.conditional.entry = entry;
conditionalCtx->context.conditional.descriptor = descriptor;
conditionalCtx->context.conditional.branchResolved = false;
conditionalCtx->context.conditional.delegateName = nullptr;
conditionalCtx->context.conditional.delegateEntry = nullptr;
if (!ctx.registry) {
conditionalCtx->context.conditional.branchResolved = true;
return true;
}
ConditionalBranchResult branch = descriptor->evaluate(descriptor->userData);
const char* delegateName = nullptr;
switch (branch) {
case ConditionalBranchResult::TRUE_BRANCH:
delegateName = descriptor->truePlaceholder;
break;
case ConditionalBranchResult::FALSE_BRANCH:
delegateName = descriptor->falsePlaceholder;
break;
case ConditionalBranchResult::SKIP:
default:
delegateName = nullptr;
break;
}
conditionalCtx->context.conditional.branchResolved = true;
conditionalCtx->context.conditional.delegateName = delegateName;
if (delegateName == nullptr) {
return true;
}
const PlaceholderEntry* delegateEntry = ctx.registry->getPlaceholder(delegateName);
if (!delegateEntry) {
DFTE_LOG_WARN("Conditional placeholder '" + String(name) + "' referenced unknown placeholder: " + String(delegateName));
return true;
}
conditionalCtx->context.conditional.delegateEntry = delegateEntry;
if (!pushPlaceholderEntry(ctx, delegateEntry, delegateName)) {
ctx.popContext();
return false;
}
return true;
}
case PlaceholderType::ITERATOR: {
if (!ctx.pushContext(RenderingContextType::PLACEHOLDER_ITERATOR, name)) {
return false;
}
RenderingContext* iteratorCtx = ctx.getCurrentContext();
iteratorCtx->context.iterator.entry = entry;
iteratorCtx->context.iterator.descriptor = static_cast<const IteratorDescriptor*>(entry->data);
iteratorCtx->context.iterator.handle = nullptr;
iteratorCtx->context.iterator.initialized = false;
iteratorCtx->context.iterator.handleOpen = false;
const IteratorDescriptor* descriptor = iteratorCtx->context.iterator.descriptor;
if (!descriptor || !descriptor->next) {
DFTE_LOG_ERROR("Iterator placeholder missing descriptor: " + String(name));
ctx.popContext();
return false;
}
return true;
}
default:
DFTE_LOG_WARN("pushPlaceholderEntry does not support placeholder type for: " + String(name));
return false;
}
}
static DeviceFrameworkTemplateRenderer::RenderOutcome processIteratorContext(DeviceFrameworkTemplateContext& ctx, RenderingContext* iteratorCtx) {
if (!iteratorCtx) {
return DeviceFrameworkTemplateRenderer::makeError();
}
const IteratorDescriptor* descriptor = iteratorCtx->context.iterator.descriptor;
if (!descriptor || !descriptor->next) {
DFTE_LOG_ERROR("Iterator placeholder missing descriptor or next handler");
return DeviceFrameworkTemplateRenderer::makeError();
}
auto& iteratorState = iteratorCtx->context.iterator;
if (!iteratorState.initialized) {
iteratorState.handle = descriptor->open ? descriptor->open(descriptor->userData) : descriptor->userData;
iteratorState.initialized = true;
iteratorState.handleOpen = descriptor->close != nullptr && iteratorState.handle != nullptr && descriptor->open != nullptr;
}
IteratorItemView view = {};
IteratorStepResult step = descriptor->next(iteratorState.handle, view);
switch (step) {
case IteratorStepResult::ITEM_READY: {
if (!view.templateData && view.templateLength == 0) {
return DeviceFrameworkTemplateRenderer::makeState(TemplateRenderState::RENDERING_CONTEXT, true);
}
const char* templatePtr = view.templateData;
if (!templatePtr) {
return DeviceFrameworkTemplateRenderer::makeState(TemplateRenderState::RENDERING_CONTEXT, true);
}
size_t templateLen = view.templateLength;
if (templateLen == 0) {
templateLen = view.templateIsProgmem ? strlen_P(templatePtr) : strlen(templatePtr);
}
if (!ctx.pushContext(RenderingContextType::TEMPLATE, iteratorCtx->name)) {
return DeviceFrameworkTemplateRenderer::makeError();
}
RenderingContext* templateCtx = ctx.getCurrentContext();
templateCtx->context.templateCtx.templateData = templatePtr;
templateCtx->context.templateCtx.templateLen = templateLen;
templateCtx->context.templateCtx.isProgmem = view.templateIsProgmem;
templateCtx->context.templateCtx.position = 0;
templateCtx->context.templateCtx.iteratorPlaceholders = view.placeholders;
templateCtx->context.templateCtx.iteratorPlaceholderCount = view.placeholderCount;
return DeviceFrameworkTemplateRenderer::makeState(TemplateRenderState::TEXT, true);
}
case IteratorStepResult::COMPLETE: {
if (iteratorState.handleOpen && descriptor->close) {
descriptor->close(iteratorState.handle);
iteratorState.handleOpen = false;
}
iteratorState.handle = nullptr;
auto outcome = DeviceFrameworkTemplateRenderer::makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.popCount = 1;
RenderingContext* parent = (ctx.renderingDepth > 1) ? ctx.getContext(ctx.renderingDepth - 2) : nullptr;
if (!parent) {
outcome.nextState = TemplateRenderState::COMPLETE;
outcome.repeat = false;
outcome.finished = true;
} else if (parent->type == RenderingContextType::TEMPLATE) {
outcome.nextState = TemplateRenderState::TEXT;
}
return outcome;
}
case IteratorStepResult::ERROR:
default:
DFTE_LOG_ERROR("Iterator placeholder reported error");
if (descriptor->close && (iteratorState.handleOpen || iteratorState.handle != nullptr)) {
descriptor->close(iteratorState.handle);
}
iteratorState.handleOpen = false;
iteratorState.handle = nullptr;
return DeviceFrameworkTemplateRenderer::makeError();
}
}
} // namespace
// Helper function to convert RenderingContextType to string
static String getContextTypeString(RenderingContextType type) {
switch (type) {
case RenderingContextType::TEMPLATE: return "TEMPLATE";
case RenderingContextType::PLACEHOLDER_DATA: return "PLACEHOLDER_DATA";
case RenderingContextType::PLACEHOLDER_TEMPLATE: return "PLACEHOLDER_TEMPLATE";
default: return "UNKNOWN";
}
}
// Helper function to log state transitions with stack state
static void logStateTransition(DeviceFrameworkTemplateContext& ctx, const String& fromState, const String& toState, const String& reason = "") {
if (!deviceFrameworkTemplateEngineLogger) {
return;
}
String msg = "State: " + fromState + " -> " + toState;
if (reason.length() > 0) {
msg += " (" + reason + ")";
}
msg += " | Stack depth: " + String(ctx.renderingDepth);
if (ctx.renderingDepth > 0) {
RenderingContext* currentCtx = ctx.getCurrentContext();
msg += " | Current: " + String(currentCtx->name) + " (type=" + getContextTypeString(currentCtx->type) + ")";
}
deviceFrameworkTemplateEngineLogger->debug(msg);
}
static String getStateName(TemplateRenderState state) {
switch (state) {
case TemplateRenderState::TEXT: return "TEXT";
case TemplateRenderState::BUILDING_PLACEHOLDER: return "BUILDING_PLACEHOLDER";
case TemplateRenderState::RENDERING_CONTEXT: return "RENDERING_CONTEXT";
case TemplateRenderState::COMPLETE: return "COMPLETE";
case TemplateRenderState::ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::makeWritten(size_t bytes, TemplateRenderState state, bool repeat) {
return {bytes, state, repeat, false, false, 0, {false, RenderingContextType::TEMPLATE, nullptr}};
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::makeState(TemplateRenderState nextState, bool repeat) {
return {0, nextState, repeat, false, false, 0, {false, RenderingContextType::TEMPLATE, nullptr}};
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::makeComplete() {
return {0, TemplateRenderState::COMPLETE, false, true, false, 0, {false, RenderingContextType::TEMPLATE, nullptr}};
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::makeError() {
return {0, TemplateRenderState::ERROR, false, false, true, 0, {false, RenderingContextType::TEMPLATE, nullptr}};
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::renderChunk(DeviceFrameworkTemplateContext& ctx, uint8_t* buffer, size_t maxLen) {
RenderOutcome outcome = makeState(ctx.state, false);
switch (ctx.state) {
case TemplateRenderState::TEXT:
outcome = consumeTemplateText(ctx, buffer, maxLen);
break;
case TemplateRenderState::BUILDING_PLACEHOLDER:
outcome = buildPlaceholderToken(ctx);
break;
case TemplateRenderState::RENDERING_CONTEXT:
outcome = emitActiveContext(ctx, buffer, maxLen);
break;
case TemplateRenderState::COMPLETE:
return makeComplete();
case TemplateRenderState::ERROR:
default:
return makeError();
}
if (!applyStackCommands(ctx, outcome)) {
ctx.state = TemplateRenderState::ERROR;
return makeError();
}
TemplateRenderState previousState = ctx.state;
if (outcome.nextState != previousState) {
String fromState = getStateName(previousState);
ctx.state = outcome.nextState;
String toState = getStateName(ctx.state);
logStateTransition(ctx, fromState, toState);
} else {
ctx.state = outcome.nextState;
}
outcome.finished = (ctx.state == TemplateRenderState::COMPLETE);
outcome.errored = (ctx.state == TemplateRenderState::ERROR);
return outcome;
}
bool DeviceFrameworkTemplateRenderer::applyStackCommands(DeviceFrameworkTemplateContext& ctx, const RenderOutcome& outcome) {
for (uint8_t i = 0; i < outcome.popCount; ++i) {
if (ctx.renderingDepth == 0) {
DFTE_LOG_ERROR("Rendering stack underflow during pop");
return false;
}
ctx.popContext();
}
if (!outcome.pushContext.active) {
return true;
}
const PlaceholderEntry* entry = outcome.pushContext.entry;
if (entry == nullptr) {
DFTE_LOG_ERROR("Missing placeholder entry for pushContext");
return false;
}
const char* name = entry->name;
switch (outcome.pushContext.type) {
case RenderingContextType::PLACEHOLDER_DATA: {
if (!pushPlaceholderEntry(ctx, entry, name)) {
return false;
}
return true;
}
case RenderingContextType::PLACEHOLDER_TEMPLATE: {
if (!pushPlaceholderEntry(ctx, entry, name)) {
return false;
}
return true;
}
case RenderingContextType::PLACEHOLDER_DYNAMIC_TEMPLATE: {
if (!pushPlaceholderEntry(ctx, entry, name)) {
return false;
}
return true;
}
case RenderingContextType::PLACEHOLDER_CONDITIONAL: {
const ConditionalDescriptor* descriptor = static_cast<const ConditionalDescriptor*>(entry->data);
if (descriptor == nullptr || descriptor->evaluate == nullptr) {
DFTE_LOG_ERROR("Conditional placeholder missing descriptor: " + String(name));
return false;
}
ConditionalBranchResult branch = descriptor->evaluate(descriptor->userData);
const char* delegateName = nullptr;
switch (branch) {
case ConditionalBranchResult::TRUE_BRANCH:
delegateName = descriptor->truePlaceholder;
break;
case ConditionalBranchResult::FALSE_BRANCH:
delegateName = descriptor->falsePlaceholder;
break;
case ConditionalBranchResult::SKIP:
default:
delegateName = nullptr;
break;
}
if (delegateName == nullptr) {
return true; // Nothing to render
}
const PlaceholderEntry* delegateEntry = ctx.registry ? ctx.registry->getPlaceholder(delegateName) : nullptr;
if (!delegateEntry) {
DFTE_LOG_WARN("Conditional placeholder '" + String(name) + "' referenced unknown placeholder: " + String(delegateName));
return true;
}
if (!ctx.pushContext(RenderingContextType::PLACEHOLDER_CONDITIONAL, name)) {
return false;
}
RenderingContext* conditionalCtx = ctx.getCurrentContext();
conditionalCtx->context.conditional.entry = entry;
conditionalCtx->context.conditional.descriptor = descriptor;
conditionalCtx->context.conditional.branchResolved = true;
conditionalCtx->context.conditional.delegateName = delegateName;
conditionalCtx->context.conditional.delegateEntry = delegateEntry;
if (!pushPlaceholderEntry(ctx, delegateEntry, delegateName)) {
ctx.popContext();
return false;
}
return true;
}
case RenderingContextType::PLACEHOLDER_ITERATOR: {
if (!ctx.pushContext(RenderingContextType::PLACEHOLDER_ITERATOR, name)) {
return false;
}
RenderingContext* iteratorCtx = ctx.getCurrentContext();
iteratorCtx->context.iterator.entry = entry;
iteratorCtx->context.iterator.descriptor = static_cast<const IteratorDescriptor*>(entry->data);
iteratorCtx->context.iterator.handle = nullptr;
iteratorCtx->context.iterator.initialized = false;
iteratorCtx->context.iterator.handleOpen = false;
if (!iteratorCtx->context.iterator.descriptor || !iteratorCtx->context.iterator.descriptor->next) {
DFTE_LOG_ERROR("Iterator placeholder missing next handler: " + String(name));
ctx.popContext();
return false;
}
return true;
}
case RenderingContextType::TEMPLATE: {
if (!ctx.pushContext(RenderingContextType::TEMPLATE, name)) {
return false;
}
RenderingContext* templateCtx = ctx.getCurrentContext();
templateCtx->context.templateCtx.templateData = static_cast<const char*>(entry->data);
templateCtx->context.templateCtx.templateLen = entry->getLength(entry->data);
templateCtx->context.templateCtx.isProgmem = true;
templateCtx->context.templateCtx.position = 0;
templateCtx->context.templateCtx.iteratorPlaceholders = nullptr;
templateCtx->context.templateCtx.iteratorPlaceholderCount = 0;
return true;
}
default:
return true;
}
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::handleTemplateCompletion(DeviceFrameworkTemplateContext& ctx) {
RenderOutcome outcome = makeState(TemplateRenderState::TEXT, true);
outcome.popCount = 1;
if (ctx.renderingDepth <= 1) {
outcome.nextState = TemplateRenderState::COMPLETE;
outcome.repeat = false;
outcome.finished = true;
return outcome;
}
RenderingContext* parentCtx = ctx.getContext(ctx.renderingDepth - 2);
if (parentCtx && (parentCtx->type == RenderingContextType::PLACEHOLDER_TEMPLATE ||
parentCtx->type == RenderingContextType::PLACEHOLDER_DYNAMIC_TEMPLATE ||
parentCtx->type == RenderingContextType::PLACEHOLDER_CONDITIONAL)) {
outcome.popCount += 1;
if (ctx.renderingDepth > 2) {
parentCtx = ctx.getContext(ctx.renderingDepth - 3);
} else {
parentCtx = nullptr;
}
}
if (!parentCtx) {
outcome.nextState = TemplateRenderState::COMPLETE;
outcome.repeat = false;
outcome.finished = true;
return outcome;
}
if (parentCtx->type == RenderingContextType::TEMPLATE) {
outcome.nextState = TemplateRenderState::TEXT;
} else {
outcome.nextState = TemplateRenderState::RENDERING_CONTEXT;
}
outcome.repeat = true;
return outcome;
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::consumeTemplateText(DeviceFrameworkTemplateContext& ctx, uint8_t* buffer, size_t maxLen) {
RenderingContext* currentCtx = ctx.getCurrentContext();
if (!currentCtx || currentCtx->type != RenderingContextType::TEMPLATE) {
DFTE_LOG_ERROR("consumeTemplateText called without TEMPLATE context");
return makeError();
}
auto& templateCtx = currentCtx->context.templateCtx;
// Empty template completes immediately
if (templateCtx.templateLen == 0) {
return handleTemplateCompletion(ctx);
}
size_t written = 0;
while (written < maxLen) {
if (templateCtx.position >= templateCtx.templateLen) {
break;
}
char c = ctx.getNextChar();
if (c == '\0') {
break;
}
if (c == '%') {
ctx.placeholderPos = 0;
ctx.placeholderName[ctx.placeholderPos++] = '%';
RenderOutcome outcome = makeState(TemplateRenderState::BUILDING_PLACEHOLDER, true);
outcome.bytesWritten = written;
return outcome;
}
buffer[written++] = c;
}
if (written > 0) {
RenderOutcome outcome = makeWritten(written, TemplateRenderState::TEXT, written < maxLen);
if (written < maxLen) {
outcome.repeat = true;
}
return outcome;
}
// No bytes written, check if template finished
if (templateCtx.position >= templateCtx.templateLen || !ctx.hasMoreData()) {
return handleTemplateCompletion(ctx);
}
// Need more data
return makeState(TemplateRenderState::TEXT, false);
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::buildPlaceholderToken(DeviceFrameworkTemplateContext& ctx) {
RenderingContext* currentCtx = ctx.getCurrentContext();
if (!currentCtx || currentCtx->type != RenderingContextType::TEMPLATE) {
DFTE_LOG_ERROR("buildPlaceholderToken called without TEMPLATE context");
return makeError();
}
bool madeProgress = false;
while (ctx.placeholderPos < sizeof(ctx.placeholderName) - 1) {
if (!ctx.hasMoreData()) {
break;
}
char c = ctx.getNextChar();
if (c == '\0') {
break;
}
madeProgress = true;
ctx.placeholderName[ctx.placeholderPos++] = c;
if (c == '%') {
ctx.placeholderName[ctx.placeholderPos] = '\0';
return resolvePlaceholder(ctx);
}
}
if (ctx.placeholderPos >= sizeof(ctx.placeholderName) - 1) {
DFTE_LOG_WARN("Placeholder name too long: " + String(ctx.placeholderName));
ctx.resetPlaceholder();
return makeState(TemplateRenderState::TEXT, true);
}
if (!ctx.hasMoreData()) {
DFTE_LOG_WARN("Incomplete placeholder at end of template");
ctx.resetPlaceholder();
return handleTemplateCompletion(ctx);
}
// We consumed data but did not finish; stay in BUILDING_PLACEHOLDER
RenderOutcome outcome = makeState(TemplateRenderState::BUILDING_PLACEHOLDER, false);
outcome.repeat = madeProgress;
return outcome;
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::resolvePlaceholder(DeviceFrameworkTemplateContext& ctx) {
const PlaceholderEntry* entry = ctx.registry ? ctx.registry->getPlaceholder(ctx.placeholderName) : nullptr;
if (!entry) {
RenderingContext* currentCtx = ctx.getCurrentContext();
if (currentCtx && currentCtx->type == RenderingContextType::TEMPLATE) {
const PlaceholderEntry* overrides = currentCtx->context.templateCtx.iteratorPlaceholders;
size_t overrideCount = currentCtx->context.templateCtx.iteratorPlaceholderCount;
for (size_t i = 0; i < overrideCount; ++i) {
if (strcmp(overrides[i].name, ctx.placeholderName) == 0) {
entry = &overrides[i];
break;
}
}
}
}
if (!entry) {
DFTE_LOG_WARN("Unknown placeholder: " + String(ctx.placeholderName));
ctx.resetPlaceholder();
return makeState(TemplateRenderState::TEXT, true);
}
RenderOutcome outcome = makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.pushContext.active = true;
switch (entry->type) {
case PlaceholderType::PROGMEM_DATA:
case PlaceholderType::RAM_DATA:
outcome.pushContext.type = RenderingContextType::PLACEHOLDER_DATA;
outcome.pushContext.entry = entry;
break;
case PlaceholderType::PROGMEM_TEMPLATE:
outcome.pushContext.type = RenderingContextType::PLACEHOLDER_TEMPLATE;
outcome.pushContext.entry = entry;
outcome.nextState = TemplateRenderState::TEXT;
break;
case PlaceholderType::DYNAMIC_TEMPLATE:
outcome.pushContext.type = RenderingContextType::PLACEHOLDER_DYNAMIC_TEMPLATE;
outcome.pushContext.entry = entry;
outcome.nextState = TemplateRenderState::TEXT;
break;
case PlaceholderType::CONDITIONAL:
outcome.pushContext.type = RenderingContextType::PLACEHOLDER_CONDITIONAL;
outcome.pushContext.entry = entry;
outcome.nextState = TemplateRenderState::RENDERING_CONTEXT;
break;
case PlaceholderType::ITERATOR:
outcome.pushContext.type = RenderingContextType::PLACEHOLDER_ITERATOR;
outcome.pushContext.entry = entry;
outcome.nextState = TemplateRenderState::RENDERING_CONTEXT;
break;
default:
DFTE_LOG_WARN("Unsupported placeholder type");
ctx.resetPlaceholder();
return makeState(TemplateRenderState::TEXT, true);
}
ctx.resetPlaceholder();
return outcome;
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::emitActiveContext(DeviceFrameworkTemplateContext& ctx, uint8_t* buffer, size_t maxLen) {
RenderingContext* currentCtx = ctx.getCurrentContext();
if (!currentCtx) {
return makeComplete();
}
switch (currentCtx->type) {
case RenderingContextType::TEMPLATE:
return consumeTemplateText(ctx, buffer, maxLen);
case RenderingContextType::PLACEHOLDER_DATA:
return streamPlaceholderData(ctx, currentCtx, buffer, maxLen);
case RenderingContextType::PLACEHOLDER_TEMPLATE:
return makeState(TemplateRenderState::TEXT, true);
case RenderingContextType::PLACEHOLDER_DYNAMIC_TEMPLATE:
return makeState(TemplateRenderState::TEXT, true);
case RenderingContextType::PLACEHOLDER_CONDITIONAL:
return makeState(TemplateRenderState::RENDERING_CONTEXT, true);
case RenderingContextType::PLACEHOLDER_ITERATOR:
return processIteratorContext(ctx, currentCtx);
default:
DFTE_LOG_ERROR("emitActiveContext encountered unknown context type");
return makeError();
}
}
DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::streamPlaceholderData(DeviceFrameworkTemplateContext& ctx,
RenderingContext* context,
uint8_t* buffer,
size_t maxLen) {
auto& dataCtx = context->context.data;
const PlaceholderEntry* entry = dataCtx.entry;
if (!entry) {
DFTE_LOG_ERROR("Placeholder data context missing entry");
RenderOutcome outcome = makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.popCount = 1;
return outcome;
}
if (ctx.registry == nullptr) {
DFTE_LOG_ERROR("Placeholder registry not set; cannot render placeholder: " + String(entry->name));
RenderOutcome outcome = makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.popCount = 1;
RenderingContext* parent = (ctx.renderingDepth > 1) ? ctx.getContext(ctx.renderingDepth - 2) : nullptr;
if (!parent) {
outcome.nextState = TemplateRenderState::COMPLETE;
outcome.repeat = false;
outcome.finished = true;
} else if (parent->type == RenderingContextType::TEMPLATE) {
outcome.nextState = TemplateRenderState::TEXT;
}
return outcome;
}
size_t totalLength = entry->getLength(entry->data);
if (dataCtx.offset >= totalLength) {
RenderOutcome outcome = makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.popCount = 1;
RenderingContext* parent = (ctx.renderingDepth > 1) ? ctx.getContext(ctx.renderingDepth - 2) : nullptr;
if (parent && parent->type == RenderingContextType::PLACEHOLDER_CONDITIONAL) {
outcome.popCount += 1;
parent = (ctx.renderingDepth > 2) ? ctx.getContext(ctx.renderingDepth - 3) : nullptr;
}
if (!parent) {
outcome.nextState = TemplateRenderState::COMPLETE;
outcome.repeat = false;
outcome.finished = true;
} else if (parent->type == RenderingContextType::TEMPLATE) {
outcome.nextState = TemplateRenderState::TEXT;
}
return outcome;
}
size_t written = ctx.registry->renderPlaceholder(entry, dataCtx.offset, buffer, maxLen);
if (written > 0) {
dataCtx.offset += written;
return makeWritten(written, TemplateRenderState::RENDERING_CONTEXT, written < maxLen);
}
// No bytes written; treat as completion and pop context
RenderOutcome outcome = makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.popCount = 1;
RenderingContext* parent = (ctx.renderingDepth > 1) ? ctx.getContext(ctx.renderingDepth - 2) : nullptr;
if (parent && parent->type == RenderingContextType::PLACEHOLDER_CONDITIONAL) {
outcome.popCount += 1;
parent = (ctx.renderingDepth > 2) ? ctx.getContext(ctx.renderingDepth - 3) : nullptr;
}
if (!parent) {
outcome.nextState = TemplateRenderState::COMPLETE;
outcome.repeat = false;
outcome.finished = true;
} else if (parent->type == RenderingContextType::TEMPLATE) {
outcome.nextState = TemplateRenderState::TEXT;
}
return outcome;
}
size_t DeviceFrameworkTemplateRenderer::renderNextChunk(DeviceFrameworkTemplateContext& ctx, uint8_t* buffer, size_t maxLen) {
if (ctx.isComplete() || ctx.hasError()) {
return 0;
}
size_t written = 0;
size_t iterations = 0;
uint8_t* writePtr = buffer;
size_t remaining = maxLen;
while (remaining > 0 && !ctx.isComplete() && !ctx.hasError() && iterations < MAX_ITERATIONS) {
RenderOutcome outcome = renderChunk(ctx, writePtr, remaining);
written += outcome.bytesWritten;
ctx.totalBytesProcessed += outcome.bytesWritten;
writePtr += outcome.bytesWritten;
remaining -= outcome.bytesWritten;
iterations++;
if (outcome.finished || outcome.errored) {
break;
}
if (!outcome.repeat && outcome.bytesWritten == 0) {
break;
}
}
if (iterations >= MAX_ITERATIONS) {
DFTE_LOG_WARN("Maximum iterations reached in renderNextChunk");
}
return written;
}
void DeviceFrameworkTemplateRenderer::initializeContext(DeviceFrameworkTemplateContext& ctx, const char* templateData) {
initializeContext(ctx, templateData, true);
}
void DeviceFrameworkTemplateRenderer::initializeContext(DeviceFrameworkTemplateContext& ctx, const char* templateData, bool templateInProgmem) {
ctx.reset();
// Push initial template context
if (!ctx.pushContext(RenderingContextType::TEMPLATE, "ROOT")) {
ctx.state = TemplateRenderState::ERROR;
return;
}
RenderingContext* rootCtx = ctx.getCurrentContext();
rootCtx->context.templateCtx.templateData = templateData;
rootCtx->context.templateCtx.templateLen = templateInProgmem ? strlen_P(templateData) : strlen(templateData);
rootCtx->context.templateCtx.isProgmem = templateInProgmem;
rootCtx->context.templateCtx.position = 0;
ctx.state = TemplateRenderState::TEXT;
logStateTransition(ctx, "INIT", "TEXT", "Initialized template context");
}
bool DeviceFrameworkTemplateRenderer::isComplete(const DeviceFrameworkTemplateContext& ctx) {
return ctx.isComplete();
}
bool DeviceFrameworkTemplateRenderer::hasError(const DeviceFrameworkTemplateContext& ctx) {
return ctx.hasError();
}