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 usingFormat::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
or5.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} KB</td>
<td>{original_size}<br>{!kb:original_size} 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 callingsetDoubleBraces(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
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 TemplateStream(IDataSourceStream *stream, bool owned = true)
-
class SectionTemplate : public TemplateStream
Provides enhanced template tag processing for use with a SectionStream.
Subclassed by IFS::DirectoryTemplate
Public Types
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
.
-
inline void onGetValue(GetValue callback)
-
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
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
-
inline SectionStream(IDataSourceStream *source, uint8_t maxSections = 5)