Template Streams

Sming provides several classes to assist with serving dynamic content:

Basic Templating

The TemplateStream class is a stream which performs variable-value substitution using {varname} style markers, which are replaced as the stream is read.

You can find a simple demonstration of how this class is used in the Bootstrap Http Server sample application.

Note

There must be no whitespace after the opening brace. For example, { varname } will be emitted as-is without modification.

This allows inclusion of CSS fragments such as td { padding: 0 10px; } in HTML without resorting to double-braces.

If necessary, use double-braces {{varname}} in your template and call TemplateStream::setDoubleBraces() (true).

Invalid tags, such as {"abc"} will be ignored, so JSON templates do not require special treatment.

Variable values can be set using TemplateStream::setVar() or TemplateStream::setVars(). These are stored in a HashMap which can be accessing directly via TemplateStream::variables().

To support calculated values and external lookups, an optional callback may be provided via TemplateStream::onGetValue(). This is invoked only if a variable is not found in the map.

Another option is to use TemplateStream as a base class and override the TemplateStream::getValue() method.

Important

If required, text must be escaped appropriately for the output format. For example, encoding reserved HTML characters can be handled using Format::Html::escape().

Advanced Templating

Introduction

The SectionTemplate class extends TemplateStream to provide more advanced dataset processing capabilities. It is intended to be used as the base class for a data provider.

One such implementation is the IFS::DirectoryTemplate class. The Basic IFS sample demonstrates how it can be used to provide a formatted directory listing in multiple formats, using a different template for each format.

The Basic Templates sample illustrates a similar approach using data from CSV data files.

If the output format requires escaping, create an instance of the appropriate Format::Formatter and call SectionTemplate::setFormatter(). If providing custom values via callback, obtain the current formatter via SectionTemplate::formatter() class and call the escape method. Note that for performance reasons this is not done automatically as often variable values do not require escaping. User-provided values or filenames must always be properly escaped.

Sections

Templates typically contain multiple sections. The IFS::DirectoryTemplate, for example, uses 3 sections for header, content and footer. The header and footer are emitted exactly once, but the content section is repeated for each available data record.

The SectionStream class is used internally so that all sections can be provided within a single file.

Sections are (by default) marked {SECTION}{/SECTION}. Everything outside of these markers is ignored, so can contain comments.

Using SectionTemplate

Implementations should provide the following methods:

nextRecord

This method is called before a new content record is about to be output. Here’s the annotated IFS::DirectoryTemplate implementation:

// Return true if we have a new valid record, false if not
bool nextRecord() override
{
    // Content section we fetch the next directory record, if there is one
    if(sectionIndex() == 1) {
        return directory->next();
    }

    // This code emits the header and footer sections exactly once
    // Returning false suppresses their output completely
    return recordIndex() < 0;
}

This sets up the ‘current’ directory information record.

getValue

Lookup values for a given field:

String getValue(const char* name) override
{
    // return ...
}

Important

If required, text must be escaped appropriately for the output format. Use SectionTemplate::formatter() to obtain the current For example, encoding reserved HTML characters can be handled using Format::Html::escape().

Control language

A basic control language is implemented using ! escaped tags. Commands may have zero or more arguments, separated by :.

  • Numbers must be decimal and start with a digit, e.g. 11 or 5.6

  • Strings must be quoted “…”

  • Sub-expressions must be contained in braces {…}

Anything else is treated as a variable name. Variable names beginning with $ are reserved for internal use. The following values are currently defined:

$section The current section index $record The current record index

Conditional if/else/endif statements may be nested.

This is the current command list:

  • {!int:A} Output A as integer

  • {!float:A} Output A as float

  • {!string:A} Output A as quoted string

  • {!mime_type:A} Get MIME type string for a filename

  • {!replace:A:B:C} Copy of A with all occurrences of B replaced with C

  • {!length:A} Number of characters in A

  • {!pad:A:B:C} Copy of A padded to at least B characters with C (default is space). Use -ve B to left-pad. C

  • {!repeat:A:B} Repeat A, number of iterations is B

  • {!kb:A} Convert A to KB

  • {!ifdef:A} emit block if A is not zero-length

  • {!ifdef:A} emit block if A is zero-length

  • {!ifeq:A:B} emit block if A == B

  • {!ifneq:A:B} emit block if A != B

  • {!ifgt:A:B} emit block if A > B

  • {!iflt:A:B} emit block if A < B

  • {!ifge:A:B} emit block if A >= B

  • {!ifle:A:B} emit block if A <= B

  • {!ifbtw:A:B:C} emit block if B <= A <= C

  • {!ifin:A:B} emit block if A contains B

  • {!ifin:A:B} emit block if A does not contain B

  • {!else}

  • {!endif}

  • {!add:A:B} A + B

  • {!sub:A:B} A - B

  • {!goto:A} move to section A

  • {!count:A} emit number of records in section A

  • {!index:A} emit current record index for section A

Note

See Sming/Core/Data/Streams/SectionTemplate.h for an up-to-date list of commands and internal variables.

Here’s an excerpt from the Basic_IFS sample, displaying information for a single file:

{!iflt:$record:100} <!-- If $record < 100 -->
    <tr>
        <td>{$record}</td>
        <td>{file_id}</td>
        <td><a href="{path}{name}"><span style='font-size:20px'>{icon}</span> {name}</a></td>
        <td>{!mime_type:name}</td>
        <td>{modified}</td>
        {!ifin:attr:"D"} <!-- Value of 'attr' variable contains "D" ->
            <td></td><td></td>
        {!else}
            <td>{size}<br>{!kb:size}&nbsp;KB</td>
            <td>{original_size}<br>{!kb:original_size}&nbsp;KB</td>
        {!endif}
        <td>{!replace:attr_long:", ":"<br>"}</td>
        <td>{compression}</td>
        <td>{access_long}</td>
    </tr>
{!else} <!-- $record >= 100 -->
    Too many records {$record}
{!endif}

API Reference

class TemplateStream : public IDataSourceStream

Stream which performs variable-value substitution on-the-fly.

Template uses {varname} style markers which are replaced as the stream is read.

Note: There must be no whitespace after the opening brace. For example, { varname } will be emitted as-is without modification.

This allows inclusion of CSS fragments such as td { padding: 0 10px; } in HTML.

If necessary, use double-braces {{varname}} in templates and enable by calling setDoubleBraces(true).

Invalid tags, such as {"abc"} will be ignored, so JSON templates do not require special treatment.

Subclassed by FSTR::TemplateStream, SectionTemplate, TemplateFileStream

Public Types

using Variables = HashMap<String, String>

Maps variable names to values.

using GetValueDelegate = Delegate<String(const char *name)>

Callback type to return calculated or externally stored values.

Public Functions

inline TemplateStream(IDataSourceStream *stream, bool owned = true)

Create a template stream.

Parameters:
  • stream – source of template data

  • owned – If true (default) then stream will be destroyed when complete

inline virtual StreamType getStreamType() const override

Get the stream type.

Return values:

StreamType – The stream type.

virtual uint16_t readMemoryBlock(char *data, int bufSize) override

Read a block of memory.

Todo:

Should IDataSourceStream::readMemoryBlock return same data type as its bufSize param?

Parameters:
  • data – Pointer to the data to be read

  • bufSize – Quantity of chars to read

Return values:

uint16_t – Quantity of chars read

virtual int seekFrom(int offset, SeekOrigin origin) override

Change position in stream.

Note

This method is implemented by streams which support random seeking, such as files and memory streams.

Parameters:
  • offset

  • origin

Return values:

New – position, < 0 on error

inline virtual bool isFinished() override

Check if all data has been read.

Return values:

bool – True on success.

inline void setVar(const String &name, const String &value)

Set value of a variable in the template file.

Note

Sets and existing variable or adds a new variable if variable does not already exist

Parameters:
  • name – Name of variable

  • value – Value to assign to the variable

inline void setVars(const Variables &vars)

Set multiple variables in the template file.

Parameters:

vars – Template Variables

inline Variables &variables()

Get the template variables.

Return values:

TemplateVariables – Reference to the template variables

inline virtual String getName() const override

Returns name of the resource.

Note

Commonly used to obtain name of file

Return values:

String

inline void onGetValue(GetValueDelegate callback)

Set a callback to obtain variable values.

Parameters:

callback – Invoked only if variable name not found in map

inline void enableOutput(bool enable)

During processing applications may suppress output of certain sections by calling this method from within the getValue callback.

inline bool isOutputEnabled() const

Determine if stream output is active.

Used by SectionTemplate class when processing conditional tags.

inline void setDoubleBraces(bool enable)

Use two braces {{X}} to mark tags.

Parameters:

enable – true: use two braces, false (default): single brace only

virtual String evaluate(char *&expr)

Evaluate a template expression.

This method is overridden by SectionTemplate to support more complex expressions.

Parameters:

expr – IN: First character after the opening brace(s) OUT: First character after the closing brace(s)

Return values:

String – Called internally and an opening brace (“{” or “{{”) has been found. Default behaviour is to locate the closing brace(s) and interpret the bounded text as a variable name, which is passed to getValue.

inline String eval(String expr)

Evaluate an expression in-situ.

Parameters:

expr – Expression to evaluate

Return values:

String

virtual String getValue(const char *name)

Fetch a templated value.

Parameters:

name – The variable name

Return values:

String – value, invalid to emit tag unprocessed

class SectionTemplate : public TemplateStream

Provides enhanced template tag processing for use with a SectionStream.

Subclassed by IFS::DirectoryTemplate

Public Types

using GetValue = Delegate<String(const char *name)>

Application callback to process additional fields.

Note

Applications should call escape() if required before returning content.

Param templateStream:

Param name:

Field name, never null

Retval String:

The field value

Public Functions

inline void onGetValue(GetValue callback)

Set a callback to be invoked.

Alternative to subclassing.

inline void setFormatter(Formatter &formatter)

Associate a text format with this template stream.

Parameters:

formatter – Provide formatter so we can call escape(), etc. as required

inline Formatter &formatter() const

Get the stream format.

Return values:

Formatter& – The formatter in effect. Default is :cpp:class:Format::Standard.

inline virtual MimeType getMimeType() const override

Get the MIME type associated with this template stream.

Return values:

MimeType – As defined by the formatter. Default is MIME_TEXT.

inline const SectionStream &stream() const

Access the underlying section stream.

Provided for debugging and other purposes. Applications should not use this method.

Return values:

SectionStream& – Wraps source stream provided in constructor

inline int sectionIndex() const

Get the index for the current section.

Return values:

int – Indices are 0-based, returns -1 if ‘Before Start’

inline uint8_t sectionCount() const

Get number of sections in source stream.

Return values:

uint8_t – Source is scanned in constructor so this is always valid

inline int recordIndex() const

Get current record index.

Return values:

int – Indices are 0-based, returns -1 if ‘Before Start’

bool gotoSection(uint8_t index)

Discard current output and change current section.

Parameters:

uint8_t – Index of section to move to

Return values:

bool – true on success, false if section index invalid

inline void onNextRecord(NextRecord callback)

Set a callback to be invoked when a new record is required.

Can be used as alternative to subclassing.

virtual String evaluate(char *&expr) override

Evaluate a template expression.

This method is overridden by SectionTemplate to support more complex expressions.

Parameters:

expr – IN: First character after the opening brace(s) OUT: First character after the closing brace(s)

Return values:

String – Called internally and an opening brace (“{” or “{{”) has been found. Default behaviour is to locate the closing brace(s) and interpret the bounded text as a variable name, which is passed to getValue.

virtual String getValue(const char *name) override

Fetch a templated value.

Parameters:

name – The variable name

Return values:

String – value, invalid to emit tag unprocessed

class SectionStream : public IDataSourceStream

Presents each section within a source stream as a separate stream.

Sections are (by default) marked {!SECTION} … {/SECTION} This is typically used with templating but can be used with any stream type provided the tags do not conflict with content.

Public Types

using NextSection = Delegate<void()>

Application notification callback when section changes.

using NextRecord = Delegate<bool()>

Application callback to move to next record.

Retval bool:

true to emit section, false to skip

Public Functions

inline SectionStream(IDataSourceStream *source, uint8_t maxSections = 5)

Construct a section stream with default options.

inline SectionStream(IDataSourceStream *source, uint8_t maxSections, const String &startTag, const String &endTag)

Construct a section stream.

Parameters:
  • source – Contains all section data, must support random seeking

  • startTag – Unique text used to mark start of a section

  • endTag – Marks end of a section

inline virtual int available() override

Return the total length of the stream.

Return values:

int – -1 is returned when the size cannot be determined

virtual uint16_t readMemoryBlock(char *data, int bufSize) override

Read a block of memory.

Todo:

Should IDataSourceStream::readMemoryBlock return same data type as its bufSize param?

Parameters:
  • data – Pointer to the data to be read

  • bufSize – Quantity of chars to read

Return values:

uint16_t – Quantity of chars read

virtual int seekFrom(int offset, SeekOrigin origin) override

Change position in stream.

Note

This method is implemented by streams which support random seeking, such as files and memory streams.

Parameters:
  • offset

  • origin

Return values:

New – position, < 0 on error

inline virtual bool isFinished() override

Check if all data has been read.

Return values:

bool – True on success.

inline size_t count() const

Get number of sections in this stream.

inline const Section *getSection() const

Get description of the current section.

Return values:

Section* – The section information, or nullptr if there is no current section

inline const Section *getSection(unsigned index) const

Get description for any section given its index.

Return values:

Section* – The section information, or nullptr if section was not found

inline void onNextSection(NextSection callback)

Register a callback to be invoked when moving to a new section.

inline void onNextRecord(NextRecord callback)

Register a callback to be invoked when moving to a new record.

bool gotoSection(uint8_t index)

Goto a new section immediately.

inline bool setNewSection(int8_t index)

Goto a new section after current tag has been processed.

struct Section