mirror of
https://github.com/alexhopeoconnor/DFTE.git
synced 2026-10-21 20:22:37 +10:00
Add DFTE sources and tests
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user