HTMLki – seamless templating with the HTML spirit
HTMLki is a standalone templating engine for PHP 5.6 and up. Unlike most today’s templating systems that treat HTML as text and insert their own directives here and there HTMLki extends HTML by adding variables, loops, language lines, etc. directly into existing HTML constructs. Any valid HTML or PHP code is also a valid HTMLki template.
For example, here’s a simple menu in HTMLki:
xml<ul $menu> <li "$classes"> <img $icon src=$icon> <a "$url" target=$target>$caption</a> </li> </endul>
This is roughly equivalent to the following PHP code:
<?php if ($menu) {?>
<ul>
<?php foreach ($menu as $item) {?>
<?php extract($item)?>
<li <?=$classes ? 'class="'.htmlspecialchars($classes).'"' : ''?>>
<?php if ($icon) {?>
<img src="<?=htmlspecialchars($icon)?>">
<?php }?>
<a href="<?=htmlspecialchars($url)?>"
<?=$target ? 'target="'.htmlspecialchars($target).'"' : ''?>>
<?=htmlspecialchars($caption)?>
</a>
</li>
<?php }?>
</ul>
<?php }?>
Features
Because HTMLki is just HTML on steroids, any text editor with HTML/PHP syntax scheme gets highlighting right most of the time.
- XSS protection – output is HTML-escaped by default
- loops and conditions – like in the above example:
xml<ul $list>
orxml<if $a == 3>
- attribute magic – automatic expansion of
xml<form file>
intoxml<form enctype="multipart/form-data">
,xml<div "span-6">
intoxml<div class="span-6">
and more - tag magic:
- shortcuts (
xml<radio>
intoxml<input type="radio">
) - multitags (
xml<thead/tr>
intoxml<thead><tr>
) - singletags (
xml<textarea />
intoxml<textarea></textarea>
) - and more
- shortcuts (
- language lines – simply text wrapped in double quotes:
xml<b>"Hello!"</b>
- expressions and variables – like
xml{ date('d.m.y') }
- compartments – automatically collect
xml$+scripts 'jquery.js'
and such over all connected templates for later output:xml<each $scripts><script "$item" /></each>
- PHP code – just as you guess:
PHP<?='string'?>
– short PHP tags expanded automatically so you don’t have to care about any particular php.ini settings - function-tags – in form of custom tags like
xml<include>
- input checking – clearly see what the template accepts: $>currentPage@1 int <= $lastPage
- most constructs can be escaped, such as ""Not a language.", {{ not_an_expr } and $$notAVar
- this list is not complete – refer to Syntax for all enhancements
The above doesn’t require any additional integration code. However, you can tailor HTMLki into a markup ideal for your particular application by adding handlers for specific tags, attributes, etc.
For example, HTMLki can automatically expand src, href and action attributes into full URLs, or have tags like
xml<errors>
that output the list of errors linked to some input field (textarea, selectbox, etc.).
Download
Grab the latest version here.
HTMLki is hosted on GitHub. Report any issues and make pull requests there.
HTMLki is released in public domain. I would appreciate attribution and a backlink, though.
Syntax
HTMLki extends HTML syntax similar to the way LESS extend CSS. Any HTML file is a valid HTMLki file but not vice-versa.
This section describes the entire HTMLki language. It’s huge in the details but they stem from a few simple concepts.
{ expr }
Expression that is evaluated as PHP code and its result output with the following HTML characters quoting (htmlspecialchars with ENT_COMPAT):
- < →
xml<
- > →
xml>
- & →
xml&
- " →
xml"
If brackets’ content follows this pattern: [a-z_][a-zA-Z0-9_] it is converted into a variable: PHP{ echoMe }
→ PHP{ $echoMe }
. In rare cases when it’s a problem add some other symbols: PHP{ ''.__NAMESPACE__ }
.
See also dollar shortcut.
Examples (spaces inside brackets are optional and are often added to improve readability):
xml<h2>{ $title }</h2> <time datetime="{ gmdate('Y-m-d') }">">
<h2><?php echo htmlspecialchars(($title)?></h2>
<time datetime="<?=htmlspecialchars((gmdate('Y-m-d'))?>">">
Trailing semicolon is optional:
xml<h2>{ $title; }</h2>
Dollar shortcut
Outputs an escaped variable content; starts with a dollar symbol ($) followed by a Latin letter and includes all consecutive a-z A-Z 0-9 _ symbols. Standalone $’s not matching this pattern are output as is.
Equivalent of wrapping this into brackets: { $var_123 }.
xmlHello, <strong>$user</strong>! This costs <em>$5</em> (USD).
Hello, <strong>{ $user }</strong>!
Hello, <strong><?=htmlspecialchars($user)?></strong>!
This costs <em>$5</em> (USD).
This costs <em><?='$5'?></em> (USD).
Attention: unlike regular PHP string syntax, $obj->prop and $arr[0] output not value of the object property or array member but value of $obj or $arr% followed by raw–>propor[0]%% strings. Use PHP insets for this:
xmlHello, <strong>{ $user->name }</strong>! Hello, <strong>{ $user['name'] }</strong>!
{= raw }
The same as bracket expression but doesn’t escape HTML characters. Should be used with care as it creates a XSS possibility.
xml<h2>{= $title }</h2> <time datetime="{= gmdate('Y-m-d') }">">
<h2><?php echo $title?></h2>
<time datetime="<?=gmdate('Y-m-d')?>">">
Ruby-like function call
Ruby allows omitting brackets around the outermost function call for readability and brevity. A similar feature is supported within { } construct:
xmlYour balance is <b>${ number_format $user->balance }</b>.
Your balance is <b>$<?=number_format($user->balance)?></b>.
{[=][ ]Func PrefixArgs[;]} Func = one or more of \w \ : - > Prefix = one of " ' $ [ \w Args = anything
The above construct is replaced by PHP<?=Func(PrefixArgs)?>
(with escaping added for non-{= form).
xmlYour balance is <b>{= Locale::currencyAsHTML User::current()->balance }</b>. Timezones: { join ', ', \DateTimeZone::listIdentifiers(\DateTimeZone::ANTARCTICA) }.
{{ escape
Double curly brackets are used to cancel their normal bracket expression processing.
xmlC-like languages use <kbd>{{</kbd> symbols a lot.
C-like languages use <kbd><?='{'?></kbd> symbols a lot.
Language
Strings enclosed in quotes are first looked up in language strings table and then output. If the table contains no such string the original is used.
Strings may contain substitutions which are replaced with :1, :2, etc. For example:
- This contains $count replaces. – language string: This contains :1 replaces..
- Results #$offset..$count out of { count($results) }. – language string: Results #:1..:2 out of :3..
Substitution characters are escaped by doubling them: This item costs {{$$5 USD}. → «This item costs {$5 USD}.».
Note: HTML characters are escaped in substitutions. See also tag attribute expansion.
xml<li>"Home"</li> <p class="none"> "Search query { htmlspecialchars($query) } returned no results." </p>
<li><?php echo lang('Home')?></li>
<p class="none">
<?=lang('Search query :1 returned no results.', htmlspecialchars($query))?>
</p>
Attention: because these are not PHP strings, "Hello, $user->name" becomes "Hello, :1->name". To reference an object field or array member use "Hello, { $user->name }".
""escape
Double quotes prevent the following text from being treated as a language string. They are output as is.
xml<em>""Hello, mister Robertson"".</em>
<em><?='"Hello, mister Robertson"'?>.</em>
Inside language strings quotes can be doubled to produce single quotes:
xml<em>"Hello, mister ""Robertson""."</em>
<em><?=$this->lang('Hello, mister "Robertson".')?></em>
Line merging
Often in HTML you have a long line which cannot be broken without unwanted side effects. The trailing-backslash syntax borrowed from C addresses this problem:
xml<a href="..." \ onclick="alert('Okay, clicked.\n\ Now what?')"> Link text\ </a>
xml<a href="..." onclick="alert('Okay, clicked.\nNow what?')"> Link text</a>
Example removing extra spaces inside HTML lists which would get in a way:
xml<span "list">\ <a href="...">Item 1</a>\ <a href="...">Item 2</a>\ </span>
xml<span class="list"><a href="...">Item 1</a><a href="...">Item 2</a></span>
Line merging happens before all other processing and can be used with constructs that are normally single-line, like PHP code, language strings and brackets:
<?='A very\
long \
string'?>
"Hello, my dear \
$user!"
{ a(brace, \
form) }
<?='A verylong string'?>
"Hello, my dear $user!"
{ a(brace, form) }
Formally, everything starting from \ until next non-whitespace symbol (on the following line) is removed. \ may be followed by any number of tabs and spaces before the line break. Whitespace before \ is preserved.
\\escape
Each pair of trailing backslashes is shrunk to a single \ and no line merging happens (unless there is an unpaired \):
C-style languages \\ might also \\\ cause the \\\\ leaning toothpick syndrome (LTS).
C-style languages \ might also \cause the \\ leaning toothpick syndrome (LTS).
Variable assignment
Apart from outputting variables using expressions and dollar shortcut you can set their values – either single-line or multi-line.
This construct must appear on a line by itself or it’s output as is. The leading dollar symbol ($) can only be preceded with whitespace.
Single-line
xml$=varName php($expression) $*varName 'assign, escape'.'& output'
The second form using asterisk (*) outputs new PHP$varName
value with escaped HTML.
xml$=maxLength 50 <input "text" maxlength=$maxLength> $*warning '<p class="warning">Warning!</p>' <ul> ... </ul> $warning
<?php $maxLength = 50?>
<input class="text" maxlength=<?=$maxLength?> />
<?php echo htmlspecialchars($warning = '<p class="warning">Warning!</p>')?>
<ul>
...
</ul>
<?php echo htmlspecialchars($warning)?>
Trailing semicolon is optional:
xml$=maxLength 50;
Multi-line
xml$=varName <pre> Any HTMLki {='code'} </pre> $^varName
The closing circumflex symbol (^) can be replaced with an asterisk (*) to output the new content after assigning it to the variable. Note: HTML is not escaped, use dollar shortcut if you need to do this.
This is useful for creating sections that can be reused in nested or parent templates. Since these are regular PHP variables you can either pass them to functions or output their previous value before overriding in a child template.
Proper nesting and matching of opening/closing variable names is not checked or enforced. The variable is assigned only when its closing pair (=*var or =^var) is met.
xml<nav> $=menu <li><a href=$home>Main page</a></li> <li><a href="about">About us</a></li> $*menu </nav> <!-- Another template file: --> $=menu <li><a href="profile">Your profile</a></li> {= $menu } $^menu
<nav>
<?php ob_start()?>
<li><a href="<?=$home?>">Main page</a></li>
<li><a href="about">About us</a></li>
<?php $menu = ob_get_flush()?>
</nav>
<!-- Another template file: -->
<?php ob_start()?>
<li><a href="profile">Your profile</a></li>
<?php echo $menu?>
<?php $menu = ob_get_clean()?>
xml$=header <header id=head> <h1>Logo</h1> $=topNav <nav $nav> <a "$item">$item</a> </endnav> $*topNav </header> $^header
As a result, PHP$nav
contains:
xml<nav> <a href="index.html">index.html</a> <a href="about.html">about.html</a> </nav>
xml<header id="head"> <h1>Logo</h1> <nav> <a href="index.html">index.html</a> <a href="about.html">about.html</a> </nav> </header>
$=attribute@tag
When variable name is followed by an at symbol (@) it sets either a flag attribute or a shorthand for given tag. Tag can be empty to set global flag attribute or shorthand – for example, $=attr@ disabled sets disabled flag attribute for all tags, such as:
-
xml<input disabled>
→xml<input disabled="disabled">
-
xml<button disabled>
→xml<button disabled="disabled">
Flag attribute form:
xml$=chk@input checked <input checkbox chk> <!-- "checkbox" is an alias of "input" so its attributes work for aliases: --> <checkbox chk> <!-- Result: --> <input type="checkbox" checked="checked">
If attribute name matches with the part before @ the former can be omitted. Both lines have the same effect:
xml$=checked@input
$=checked@input checked
Without defining chk as a flag attribute its value won’t be treated as a boolean flag. Compare:
xml<input chk> <input chk=1> <input chk="woosh"> <!-- Result: --> <input chk="chk"> <input chk="1"> <input chk="woosh"> $=chk@input <input chk="chk"> <input chk="chk"> <input chk="chk"> $=chk@input checked <input checked="checked"> <input checked="checked"> <input checked="checked">
Shorthand setting form:
xml$=print@meta media=print <meta print> <!-- Result: --> <meta media="print"> $=ext@a target=external <a ext href="http://google.com"> <!-- Result: --> <a target="external" href="http://google.com"> $=blank@ someattr= <foo blank> <!-- Result: --> <foo someattr="">
$>input@checking
A default template is an amorphous blob with no input semantics – it takes what is given and in whatever form. Input checking is used to make a template more standalone, more robust to changes outside of it and have a clearly defined interface.
xml$>lastPage int $>currentPage@1 int <= $lastPage Current page is $currentPage. <a ${ $currentPage > 1 }?>Go back</enda> <a ${ $currentPage < $lastPage }?>Go next</enda>
The first two lines are roughly equivalent to:
if (!isset($lastPage) or !is_int($lastPage)) { throw ...; }
if (!isset($currentPage)) { $currentPage = 1; }
if (!is_int($currentPage) or !($currentPage <= $lastPage)) { throw ...; }
By convention, all checks for template’s input variables are placed on top of the file. The syntax is similar to other variable assignments:
$>inputVar[@[default]] [type[!] | any] [of smth] [[if ]cond]
The shortest form is just $>someVar setting type to any and omitting all other parts.
- default specifies the value used in case the input variable is unset. default lasts until next whitespace: $>number@rand()%16 int is fine but $>foo@'a b' str is invalid. @ without default infers the default from type. Without @... part the template fails if the input variable is unset. If input is unset or null and @ is blank or null then type and cond checks are bypassed and the value is left at the type’s default (@ alone) or null (@null).
- type specifies expected variable type or its alias (below). Without ! the value is attempted to be coerced if its type differs (making ! similar to PHP’s strict_types declaration). If input variable’s type differs and couldn’t be coerced – an error occurs if @ part is omitted, else a warning occurs and the default value is used (and checked according to cond).
- of starts a type hint/comment lasting until next whitespace (ignored). Examples: $>ids array of int, $>user object of Model\User.
- if and cond specify additional check(s) as PHP code lasting until line end. The code is evaluated within the same context of the template, failing on loose false. Without if the $inputVar is prepended: $>url str !== '' is the same as $>url str if $url !== '' (ensuring
PHP$url
is not blank).
List of common types, aliases and implied defaults:
any | special type disabling type checking |
---|---|
boolean | also bool; default is PHPfalse |
integer | also int, num; default is PHP0 |
float | also double, real; default is PHP0.0 |
string | also str; default is PHP'' |
array | also hash, map; default is PHP[] |
object | also obj; default is PHPnew stdClass |
callable | note that not only closures are callable but also things like PHP'trim' , PHP['My', 'method'] and PHP$obj (if implements PHP__invoke() ) |
resource | also res |
other | null, scalar, dir, file, etc. |
A «type» is a name for which a top-level PHP function PHPis_XXX($value)
is defined. Types without defaults use PHPnull
as one.
bool | if null or scalar: if string representation is one of PHP'' 0 1 then PHP(bool) $value |
---|---|
integer | if null or FILTER_VALIDATE_INT: PHP(int) $value |
float | if null or integer or FILTER_VALIDATE_FLOAT: PHP(float) $value |
string | if null or scalar: PHP(string) $value |
Other types and other cases of the listed types are not coercible and fail the input check.
Attention: FILTER_XXX (of PHP filter_var()) trim their input so that PHP"\t123"
is seen as a valid int and float.
$+compartment
Compartments are regular variables that are automatically passed between connected templates while accumulating values. Typical examples are «styles» and «scripts» on a webpage – one template may add the styles it depends on, another template – another set of styles until the final template outputs these compartments to the appropriate places on the page (
xml<head>
and
xml<body>
).
A compartment variable is defined using a syntax similar to multi-line assignment, terminating with a $+var@key line. If key is set PHP$var
gets at most one entry per that key. A special blank key clears old values. PHP$var
is automatically created as or converted to an array.
Two main functions power this mechanism:
- Include merges compartments of the included template with the calling template’s
- Rinclude takes compartments from the calling template without changing them
Note: if there’s no output between the two lines the variable is prepared but no item is created:
xml$=head
$+head
$=head
$+head@
Roughly equivalent to this PHP 7.0+ code:
$head = (array) $head ?? [];
$head = []; // but unlike $=head [], also declares $head as a compartment.
Example
Let’s consider this «main» template (called by a frontend script/controller with page-specific variables):
xml$=head <link "this-page-styles.css"> $+head <?php var_dump($head)?> <table $rows> <include "inner"> </endtable> <?php var_dump($head)?> <rinclude "outer">
The first PHPvar_dump()
indicates that PHP$head
is an array with at least one member: PHP'<link href="..." ...>'
. If this template has been Rinclude’d by some other, PHP$head
may contain more items.
Example of the inner template:
xml<tr> <td>$item</td> </tr> $=head <script "super-table.js"> $+head@super-table
The second PHPvar_dump()
indicates that PHP$head
has at least two members now: the old PHP'<link ...>'
and a new PHP'<script src="..." ...>'
. However, even if inner is included many times there will be only one script entry because of the key used: @super-table.
Note: as a rule, @key must be unique across all connected templates – if values for this key are different it’s hard to predict which version will survive multiple inclusions.
The outer template might start with this snippet:
xml$>head@ array <!DOCTYPE html> <html> <head> <each $head> {= $item } </each> <!-- or even just: --> {= join $head }
The input checking line is optional and enforces PHP$head
’s type as well as sets its default value (empty array).
The $+var@ form is useful to wrap a compartment’s contents before passing it to another template:
xml$=body <div id="content"> <!-- $body is expected to exist and be an array --> {= join $body } </div> $+body@ <rinclude "complete-page">
If $^var was used $body would become a string leading to type errors (array expected by the likely join($body) in complete-page).
Single-line
Single-line form without opening $=var can be used to add/set an array’s member:
xml$+scripts 'jquery-2000.js' $+styles@print 'media-print.css' $+messages@ 'Old messages: '.join(', ', $messages)
$scripts = array_merge($scripts ?? [], ['jquery-2000.js']);
$styles = array_merge($styles ?? [], ['print' => 'media-print.css']);
$messages = array_merge($messages ?? [],
['Old messages: '.join(', ', $messages)]);
This form is unlike regular variable assignment which replaces the entire variable: PHP$=array 'value'
equals PHP$array = 'value'
.
$#configuration
This construct is used to change the templater’s runtime configuration. It’s similar to creating tag attributes but works with any option (see PHPHTMLki\Config
class).
xml<"">hello</> $#defaultTag 'b' <"">world</> $#evalPrefix 'namespace MyNS;' <input value={ __NAMESPACE__ }>
xml<span>hello</span> <b>world</b> <input type="text" value="MyNS">
$$=escape
It’s possible to escape the variable assignment expressions by doubling the leading dollar symbol ($).
Note that the assignment is only recognized when it occurs on a line by itself so you don’t need to escape it if it doesn’t (and escaping won’t be removed there).
xml This is not an assignment:
$$=var
Inline escaped dollar symbol: $$=var
This is not an assignment:
<?='$$=var'?>
Inline escaped dollar symbol: <?='$=var'?>
xml$=multilineVar
Content goes here...
$$^multilineVar
...and continues here as well.
Now it ends:
$^multilineVar
<?php ob_start()?>
Content goes here...
$^multilineVar
and continues here as well.
Now it ends:
<?php $multilineVar = ob_get_clean()?>
<?php code?>
It’s possible to use shorthand PHP tags regardless of short_open_tag configuration. The following tags are recognized:
PHP<?some($code)?>
→PHP<?php some($code)?>
PHP<?php some($code)?>
→PHP<?php some($code)?>
(the same)PHP<?=some($code)?>
→PHP<?php echo some($code)?>
PHP code snippets can be used along with normal HTML or HTMLki constructs; for example:
<ul "listing" $list>
<li><a rel="nofollow">{ $title }</a></li>
<?=Listing::nested($nested)?>
</ul>
Tags
Tags in HTMLki are just like normal HTML tags with certain additions:
- Default tag names – if you use spans or other tags a lot you can as well write
xml<"b-e_m">forever</>
to getxml<span class="b-e_m">forever</span>
. - Default attribute names – lets you omit src= for images, action= for forms, class= for other tags, etc.
- Flag attributes – it’s possible to specify readonly, checked, submit (
xmltype="submit"
) with a value alone and the attribute name will be guessed. - Attribute expansion – lets you use PHP variables or expressions in attribute names and values and omit surrounding quotes around attribute values:
xml<radio "radio {$i % 2 ? 'even' : 'odd'}" checked=checked id=$ctlID>
. - Multitags – lets you avoid
xml<table><tbody><tr class="odd">
by usingxml<table/tbody/tr "odd">
. Works for closing tags as well. - Shortcut tags – create
xml<input type="checkbox" name="autologin">
from justxml<checkbox "autologin">
. - Enumeration attributes – list conditions to construct the attribute’s value, turning
xml<a class=$current? class=$disabled?>
intoxml<a class="[current] [disabled]">
. - Single tag convertions – automatic turning of
xml<link>
intoxml<link />
in XHTML and ofxml<textarea rows=10 />
intoxml<textarea rows="10"></textarea>
in XHTML/HTML 5. - Loops – turns a tag into a container for a set of items and adds «else-if» and «else» branches to such tags.
- Function calling – HTMLki tags (
xml<tag attr="value">
) don’t have to be defined in the HTML spec, they can be user-defined functions such asxml<file "upload">
,xml<each $list>
orxml<include "partials/sidebar">
.
By default all tags are single-line. If multi-line tags are enabled, the <a /))...'> below is treated as a single tag:
xml<script> if (s.match(/<a /)) { s += '>'
To avoid ambiguity in closing > detection, surround > with spaces to make it part of the attribute string:
- <if a->b> is a single tag (a special exception)
- <if a > b> is a single tag
- <if a >= b> is a single tag
- <if a>b> are a tag <if a> followed by plain text b>
Attributes
HTMLki extends HTML format for specifying tag attributes (
xmlattr="value" second="yet another"
) with the following:
- Array variables for loops are specified after the tag name:
xml<div $array $another>
- Default attributes are specified the same way:
xml<img "src" "class">
. They must go after array variables for tags not listed in loopTags config option:xml<img $glyphs "src" $thumbs "class">
is not the same asxml<img "src" "class" $glyphs $thumbs>
(loop won’t be recognized) unless img is listed in loopTags – in this case both examples have the same effect. - After the above go regular attributes.
Regular attributes are separated using spaces (just like in HTML) and generally have key=value form. However:
- The key part can be omitted creating an unnamed attribute which can be used by some tag functions (similar to passing parameters to functions by index in PHP:
PHPfoo('a1', $a2)
). - You can create unnamed attribute beginning with either $ or " – to avoid confusion with loops and default attributes use =$value... form (thus = is explicit but key is empty).
- once the first such attribute is present you don’t have to prefix all the following with = as loops/default attributes have to go before regular attributes and won’t be recognized at later positions anyway.
- Both key and value parts can’t contain spaces unless one of them or both are wrapped in quotes (again, just like in HTML but HTML only allows wrapping of values while HTMLki lets you wrap both):
xml<img src=controls/button.gif>
,xml<img src="templates/Calm Gray/button.gif">
. Like language strings such attributes can contain variables or nested PHP expressions:xml<radio "$checked">
(contents ofPHP$checked
variable is used both as attribute name and value; if it’s empty nothing will be output). Note that without the quotes this will pass a list variable.- with usual tags keys are regular HTML attributes so spaces in them are not used; however, some user functions might use them.
- watch out for unwanted substitutions:
xml<input pattern="^\d{4}$">
– in order for {4} to be not treated as a code inset, double the opening bracket: ^\d{{4}$.
- Similar to above: key and/or value can be wrapped in curly brackets ({ }) instead of quotes to execute custom PHP code (must return a scalar, not an array). This works similarly to bracket construct ({ foo('bar') }). HTML symbols will be automatically quoted unless the opening bracket is immediately followed by an equality sign (=):
xml<img src={ urlOf('My Template', 'btn.gif') }>
(spaces inside brackets are optional). More examples:-
xml<radio { $checked ? 'checked' : '' }>
-
xml<a {$anchorUsingName ? 'name' : 'id'}=$anthor>
– this will outputxml<a name="some_anchor">
ifPHP$anchorUsingName
is true andxml<a id="some_anchor">
if it’s false -
xml<a attr={=$asIs}>
– the value ofPHP$asIs
variable is inserted into attr attribute ofxml<a>
tag but it’s not escaped and thus if it contains " or other special symbol the markup will be broken. -
xml<input { $off ? 'autocomplete=off' : '' }>
– likely an error as the string is used both as key and value producingxml<input autocomplete="off=autocomplete=off">
. Correct:xml<input autocomplete={ $off ? 'off' : null }>
. - most custom functions only support simple key format:
xml<each $list { ++$key }=...>
would be given raw { ++$key } as a string attribute, not incremented $key’s value.
-
- If neither { } nor " are used to wrap key or value they are processed as if wrapped in " but ending on a first whitespace or > character.
-
xml<input value=$obj->prop>
– same asPHP<input value="<?=$obj-?>">prop>
. Correct:xml<input value={ $obj->prop }>
orxml<input value="{ $obj->prop } and foo">
. -
xml<input value=$a['k']>
– same asPHP<input value="<?=$a?>['k']">
. Correct:xml<input value={ $a['k'] }>
orxml<input value="{ $a['k'] } and foo">
(but notxmlvalue="$a[k]"
orxmlvalue="$a['k']"
as these are not PHP strings). - special case: value of form $identifier? as in
xml<a class=$cur?>
becomes identifier if it’s loosely true, otherwise null; mostly useful for enumeration attributes like class.
-
- If an unnamed attribute’s value is an array it’s expanded:
xml<a "url" $extra>
becomesxml<a href="url" class="c" target="_blank">
ifPHP$extra
isPHP['class' => 'c', 'target' => '_blank']
. Warning:xml<a $extra>
is treated as a loop tag; use:xml<a =$extra>
(read above for =value syntax). - If a key is an array multiple attributes are created with the same value:
xml<input { ['autocomplete', 'autocorrect'] }=off>
.
Note: HTML characters are escaped in substitutions. See also tag attribute expansion.
Default attributes
If a tag attribute is unnamed and wrapped in quotes it’s treated as a value for some commonly used attribute of that particular tag. For example,
xml<img>
doesn’t make sense without src. One tag can have multiple default attributes.
List of default attributes per tag:
a | href, class |
---|---|
base | href |
button | name, class |
embed | src, class |
form | action, class |
img | src, class |
input | name, class |
link | href |
meta | name, content |
object | data, class |
optgroup | label, class |
option | value |
param | name, value |
script | src |
select | name, class |
source | src |
style | media |
textarea | name, class |
track | src |
Others have a single default attribute – class; for example:
xml<span "highlight">Notice</span>
→
xml<span class="highlight">
.
Flag attributes
In HTML attributes like readonly and selected can be either unspecified or have the same value as their name. With HTMLki you can specify them as flags where empty string or string «0» are false and everything else is true. They will be expanded to full HTML values.
List of flag attributes per tag:
area | nohref |
---|---|
audio | autoplay, controls, loop |
button | autofocus, formnovalidate |
command | checked |
details | open |
frame | noresize |
hr | noshade |
img | ismap |
input | autofocus, checked, readonly, formnovalidate, required |
form | action, class |
keygen | autofocus, challenge, disabled |
option | selected |
object | declare |
script | defer |
select | multiple |
style | scoped |
th, td | nowrap |
textarea | readonly |
time | pubdate |
track | default |
video | autoplay, controls, loop, muted |
Also, the following attributes can be used in any tag:
disabled | HTML-compliant targets are
xml<command> ,
xml<input> ,
xml<optgroup> ,
xml<option> ,
xml<select> and
xml<textarea> . |
---|---|
hidden | hides any element in HTML 5 |
In some tags it’s possible to define attributes given just their value. For example,
xml<form post enctype="text/plain">
is the same as
xml<form method="post" enctype="text/plain">
(note that
xml<form "post">
would be
xml<form action="post">
because of default attributes).
List of shorthand values per tag:
a | new expands into
xmltarget="_blank" rel="noopener" |
---|---|
button | type: button, reset, submit;
xmltype="button" ,
xmlvalue="1" are implied |
command | type: checkbox, command, radio |
input | type: button, checkbox, file, hidden, image, password, radio, reset, submit, text; selectonfocus is expanded into
xmlonfocus="this.select()" ;
xmltype="text" is implied |
keygen | keytype: rsa, dsa, ec |
form | method: get, post; aliases file, upload and multipart are expanded into
xmlenctype="multipart/form-data" ;
xmlmethod="post" accept-charset="utf-8" are implied |
li | type: disc, square, circle |
link |
xmlrel="stylesheet" is implied |
param | valuetype: data, ref, object |
script | xml:space: preserve;
xmltype="text/javascript" is implied |
style |
xmltype="text/css" is implied |
textarea | selectonfocus is expanded into
xmlonfocus="this.select()" ;
xmlcols="50" rows="5" are implied |
Also, the following attributes can be used in any tag:
align | left, center, right, justify, top, middle, bottom |
---|---|
dir | ltr, rtl |
Multitags
Slash symbol (/) is used to separate multiple tag names inside a single tag construct. All attributes are related to the last tag in the chain. Preceding tags cannot be given attributes using this form; however, their implied attributes (e.g.
xmlmethod="post"
for
xml<form>
) are output.
xml<table/thead/tr class="head"> <th>#</th> <th>Title</th> </tr/thead> <tbody> ... </tbody/table>
xml<form/p "info"> <label>"Your name:" <input "name"></label> </p/form>
xml<table> <thead> <tr class="head"> <th>#</th> <th>Title</th> </tr> </thead> <tbody> ... </tbody> </table>
xml<form method="post"> <p class="info"> <label>Your name: <input type="text" name="name"></label> </p> </form>
Shortcut tags
Allows creating custom tags by aliasing other tags and possibly setting attributes when referred to them this way.
xml<get "settings"> <checkbox "autologin"> </get>
xml<form method="get" action="settings"> <input type="checkbox" name="autologin"> </form>
This creates complete aliases so default attributes, flags, etc. can be used with these shortcuts as well.
Attributes defined in that tag take priority over such aliased attributes so that
xml<get "settings" method=post>
becomes
xml<form method="post" action="settings">
.
password |
xml<input type="password"> |
---|---|
hidden |
xml<input type="hidden"> |
file |
xml<input type="file"> |
check |
xml<input type="checkbox"> |
checkbox |
xml<input type="checkbox"> |
radio |
xml<input type="radio"> |
submit |
xml<button type="submit"> |
reset |
xml<button type="reset"> |
get |
xml<form method="get"> |
post |
xml<form method="post"> |
Default tags
By default if a <tag> construct is missing the tag name span is used. Other properties like default attributes work as usual.
xmlI use <"emphasis">so many</> spans that it <"hinting">can</> become very tedious to type. <"" id=endofstory />
xmlI use <span class="emphasis">so many</span> spans that it <span class="hinting">can</span> become very tedious to type. <span id="endofstory"></span>
See also configuration for changing default tag(s) used on the fly:
xml$#defaultTag 'b/i' <"">Emphase me</>
xml<b><i>Emphase me</i></b>
Enumeration attributes
Several attributes in HTML, most notably class, are space- or comma-separated values. Constructing them with PHP is very tedious.
Normally, when a duplicate attribute appears its last value is used; with enumeration attributes all loosely true values are joined using the attribute’s separator.
xml<table $rows> <tr class=$isFirst? class={ in_array($id, $matches) ? 'match' : '' }> ...
The first class is defined using special ? value suffix which returns the variable’s name if it’s loosely true, otherwise null.
Skipping cycle initialization the second line is equivalent to:
<tr class="<?=$isFirst ? 'isFirst' : ''?>
<?=in_array($id, $matches) ? 'match' : ''?>">
List of enumeration attributes per tag (space-separated unless noted otherwise):
a | rel |
---|
Others have a single enumeration attribute – class.
Tags-functions
xml<[/]tag[ attributes....]>
construct is a function call that can be intercepted. By default if no implementation is defined an HTML tag is output with that name and with all attributes joined together, respecting loops if any list variable is given.
Function and tag names are converted to lower case:
xml<SpAn>
is identical to
xml<span>
.
Each function is given a PHPTagCall
object with the list of given attributes, variables in the current template scope and other information.
There are 3 tag forms: opening (
xml<tag>
), closing (
xml</tag>
) and single (
xml<tag />
); by default they affect the resulting HTML tag form and the closing tag.
If it’s a block function (like
xml<each>
or
xml<if>
) it can return an array to be iterated over; each member is an array of variables to be defined in each iteration. See also loops.
See also List of standard functions.
Loops and conditions
Any HTML tag can be converted into a loop by listing one or more variables or expressions after its tag name, possibly mixed with default attributes:
xml<ol $headers> <li>$key = $value</li> </ol>
To use an expression simply wrap it inside ${ ... }. Also, it’s possible to specify prefix that will be added to every loop variable so they don’t collide with nested loops. If none is specified variables are defined with their original names.
Let’s assume that PHP$headers
is an array of arrays containing key and value keys:
xml<ol ${ array_filter($headers) }> <li>$key = $value</li> </ol> <!-- With a prefix: --> <ol $prefix{ array_filter($headers) }> <li>$key_prefix = $value_prefix</li> </ol>
When a non-traversable variable (neither an array nor an object implementing PHPTraversable
interface) is looped over it gets converted to an array like this:
- null and false become an empty array.
- everything else becomes an array with one member – that variable.
xml<div ${ isAuthenticated() }> "Hello, $userName!" </div>
Boolean suffix
Often you want to iterate once and only if the value is loosely true. For this, append ? after the list variable:
xml<div ${ isAuthenticated() }?>
xml<div ${ !!isAuthenticated() }>
<?php if (!!isAuthenticated()) {?>
<div>
Without ? if PHPisAuthenticated()
returns PHP0
or PHP''
the
xml<div>
will be still output according to the rules. The question mark forces a boolean cast (using PHP rules) before array convertion, resulting in either 0 or 1 iterations and no loop variables.
It works the same with variables:
xml<div $isAuth?>
. Undefined variables trigger errors as usual.
There must be no spaces before ? or it will be treated as a separate attribute:
xml<div ${ isAuthenticated() } ?> <div $isAuth ?>
Variables
The following variables are defined after the loop has started iterating:
- $key_prefix – key of list item being iterated over.
- $i_prefix – similar to
PHP$keyPREFIX
but contains 0-based index of currently iterated item regardless of its array key. - $isFirst_prefix, $isLast_prefix – boolean variables indicating if current iteration is a first/last one.
- $isEven_prefix (0th, 2nd, 4th, etc.),, $isOdd_prefix (1st, 3rd, 5th, etc.) – one of these boolean variables will be true on any iteration.
- $item_prefix – current item as it is – it can be of any type.
- if current item is an array all of its members are defined as if they were extract()’ed with added prefix (preserving standard variables above); those with wrong keys (non-identifiers or like standard) are still accessible via $item.
Prefix above stands for a string given to the tag along with the loop expression/variable. If it’s empty then no separating underscore (_) is present.
No variables are created if prefix is _ or boolean suffix is used – such loops won’t interfere with other loops’ variables. In this example only tbody’s variables are created but td’s and ul’s are not:
xml<tbody $rows> <td $flag?> <ul $_{ getItems() }>
Branching
Each loop can contain zero or more else branches. Unless it’s an implicit loop tag each loop must be terminated with a symmetrical
xml</endXXX>
form where XXX must match the starting tag. The ending XXX tag is output if both the accepted else branch and end had tag names:
xml<elseXXX>...</endXXX>
.
xml<ul $menu> <li><a "$url">$title</a></li> <elseul $secondaryMenu "secondary"> <li><a "$url">$title</a></li> <else> <p "none">"No menu items defined."</p> </endul>
For this reason all else branches with list(s) must have the same opening tag (elseul above) or tag nesting will be broken. For example:
xml<ul $menu> ... <elsediv $secondary> ... <else> <p "none">...</p> </endul>
In this example if there were no PHP$menu
items
xml<div>...</ul>
is generated thus breaking element nesting. This won’t happen for
xml<p "none">
because it has no lists (parameter starting with $).
xml<ul $menu> ... <elseul $secondary> ... <else> <p "none">...</p> </endul>
xml<ul $menu> ... <else> <div $secondary> ... <else> <p "none">...</p> </enddiv> </endul>
The difference between
xml</endul>
and
xml</ul>
is that the former ends looping and outputs the closing tag if at least one iteration had taken place while the latter always outputs the closing tag.
Both
xml<else>
and
xml<end>
can omit the tag name – this is usually used with custom tags like Each or If. In this case no ending tag is output even if a list has been iterated over.
HTMLki errors if opening tag doesn’t match
xml<endXXX>
tag (no such check is performed for non-list tags). The check is skipped for pairs ending on
xml</end>
so it’s recommended to use
xml</endXXX>
or
xml</XXX>
(for functions) where possible:
xml<each $a> <if $key> ... </if> </each> <each $a> <if $key> ... </endif> </endeach> <!-- it won't be detected if the opening <each> and <if> change places --> <each $a> <if $key> ... </end> </end>
Single-tag loops
Sometimes it’s convenient to loop over a single tag instead of a block. Let’s assume that we have an array of stylesheets:
[
'screen' => 'styles.css',
'print' => 'print.css',
];
We can output them all using this line:
xml<link $styles "$item" media=$key />
Trailing / is optional; it makes the single-tag nature of the loop more obvious to the (human) reader.
Block loops
Unlike single-tag loops block loops operate on an arbitrary chunk of code wrapped into
xml<tag>...</endtag>
pair. Regular variables are defined both inside these blocks and after them, although the latter should be considered an undesirable side-effect and might be changed in future.
Implicit loops
HTMLki can be configured to treat specific tags as always looping even if they don’t have any PHP variables passed. Of standard functions If and Each are such tags.
Both of the following examples have identical effect except the first works even when If isn’t configured as an implicit loop tag:
xml<if $var = is_scalar($var)> $$var is scalar. </endif>
xml<if is_scalar($var)> $$var is scalar. </if>
Single tags
HTMLki can automatically treat certain tags as single tags requiring no ending tag and convert single tags specified in the template into their full forms with empty body if they’re not real short tags per HTML specification.
xml<link href=styles.css> <script src=engine.js />
<link href="styles.css" />
<script src="engine.js"></script>
You can still write regular HTML if you want which is equivalent to the code above.
Default list of single tags: area, base, basefont, br, col, frame, hr, img, input, link, meta, param – plus these functions: Lang, Include.
Standard functions
Include
xml<include "partials/sidebar" expanded=1 user>
Include creates a new template, passes it certain variables according to the given regular attributes and merges its compartment variables on return.
xml<include "..." [0]> <include "..." [0 arrayvar] [[srcvar-]tplvar][=value] [...]>
- with no arguments passes the same variables defined by the time Include is called (like PHP’s include)
- if the only argument is 0, passes no variables
- if 0 is followed by an argument, that one argument is an array of values (its keys may be overridden by subsequent arguments)
- other arguments: var=val sets a scalar inside the template, src-tpl passes $src from this scope as $tpl to the template, tpl passes $tpl under the same name (like tpl-tpl)
- undefined variables generate a warning
Example of setting a non-scalar using a temporary variable (my/tpl gets PHP$theNameInside
):
xml$=a [1, 2, 3] <include "my/tpl" a-theNameInside>
Looping form:
xml<include $list [$list_2 [...]] "tpl_name" [attributes...]>
Includes another template file as if its contents was directly placed at the point of
xml<include>
.
Rinclude
xml$=body <h1>Welcome</h1> $+body <rinclude "page">
Reverse include – the same as Include except that compartment variables are passed forward to the included script and are not merged on return.
Each
xml<each $items "item"> <b>Key:</b> $key_item, <b>Index:</b> $i_item, <b>Value:</b> $item <else> <p>"No $$items!"</p> </end>
The same as Include but instead of filling a separate template file uses its own code block.
If
xml<if date('d') == 1> <p>First day today!</p> <elseif date('d') == 32> <p>You gotta be kiddin'?!</p> <else> <p>Today is { date('d.m.Y') }.</p> </end>
Takes an arbitrary PHP expression and either executes its block or ignores it (passing on to else branches if there are any).
Lang
xml<lang "install_$status" { $fileCount + 1 }>
Outputs a language string with given name. Unlike language construct ("Anything $here.") it doesn’t replace PHP$variables
and { code } with placeholders but uses that evaluated string as language string’s name.
For example, if PHP$status
above is success then Lang will output language string install_success replacing placeholders (:1, :2, etc.) with given unnamed attributes. In this example :1 is replaced with { $fileCount + 1 } thus install_success string can look like this:
Installation has been successfully finished (:1 files copied).
Mailto
xml<mailto "master@example.com" "Subject line" /> E-mail the <mailto "master@example.com">webmaster</mailto>
Outputs a «mailto» link with e-mail address obfuscated using HTML entities (&#...;). Takes up to two default attributes:
- E-mail address;
- Optional subject line
xml<a href="mailto:master@example.com?subject=Subject%20line">master@example.com</a>