A lightweight template parser used by PyroCMS.

Related tags

Templating lex
Overview

Lex

Build Status

Lex is a lightweight template parser.

Lex is released under the MIT License and is Copyrighted 2011 - 2014 PyroCMS Team.

Change Log

2.3.2

  • Convert objects with ->toArray() at the beginning of the parser() method only.
  • As much as we want to say goodbye to PHP 5.3, we brought it back for now.

2.3.1

  • Added an ArrayableInterface that automatically converts objects to arrays. This allows an opportunity to define how an object will be converted to an array by adding a ->toArray() method.
  • Looped data is now checked if it is an array or if it implements \IteratorAggregate. This prevents "invalid arguments in foreach" errors while allowing arrays and Collection objects to be iterated.
  • Dropped support for PHP 5.3

2.3.0

  • Added support for self-closing Callback Tags (e.g. {{ foo.bar /}} instead of {{ foo.bar }}{{ /foo.bar }}).
  • Rolled back the change to make Callback Tags less greedy. Callback Tags are now greedy again. If you want to use both Single and Block tags in the same template, you must close the Single tags.

2.2.3

  • Fixes issue which caused all callbacks to be processed as single tags, even if they were a block.

2.2.2

  • Fixed #7 - Conditionals inside looped data tags now work as expected.

2.2.1

  • Changed injectNoparse to be static.

2.2.0

  • Fixed a test which was PHP 5.4 only.
  • Added PHPUnit as a composer dev requirement.
  • Added a Lex\ParsingException class which is thrown when a parsing exception occurs.

2.1.1

  • Fixed an issue where strings returned by callbacks inside a comparison conditional were being processed incorrectly, causing the conditional to always fail.

2.1.0

  • Undefined variables in a conditional now evaluate to NULL, so {{ if foo }} now works properly.
  • Added the exists keyword.
  • Added the not keyword.

2.0.3

  • Fixes composer autoloading.
  • Moved classes into lib folder.

2.0.2

  • Fixed a bug introduced in 2.0.1 where NULL variables were not being displayed.

2.0.1

  • Fixed a bug where variables with a "falsey" (e.g. 0, "0", -1, etc.) value were not displayed.

2.0.0

  • All code now follows PSR-0, 1 and 2.
  • Lex_Parser has been moved to the Lex namespace and renamed to Parser.
  • Lex_Autoloader has been removed. It is now PSR-0 compliant.
  • Added the support for {{ unless }} and {{ elseunless }}.

Basic Usage

Using Lex

Lex is a Composer package named pyrocms/lex. To use it, simply add it to the require section of you composer.json file.

{
    "require": {
        "pyrocms/lex": "2.2.*"
    }
}

After adding Lex to your composer.json file, simply use the class as normal.

$parser = new Lex\Parser();

Using Lex

Basic parsing of a file:

$parser = new Lex\Parser();
$template = $parser->parse(file_get_contents('template.lex'), $data);

You can also set the Scope Glue (see "Scope Glue" under Syntax below):

$parser = new Lex\Parser();
$parser->scopeGlue(':');
$template = $parser->parse(file_get_contents('template.lex'), $data);

To allow noparse extractions to accumulate so they don't get parsed by a later call to the parser set cumulativeNoparse to true:

$parser = new Lex\Parser();
$parser->cumulativeNoparse(true);
$template = $parser->parse(file_get_contents('template.lex'), $data);
// Second parse on the same text somewhere else in your app
$template = $parser->parse($template, $data);
// Now that all parsing is done we inject the contents between the {{ noparse }} tags back into the template text
Lex\Parser::injectNoparse($template);

If you only want to parse a data array and not worry about callback tags or comments, you can do use the parseVariables() method:

$parser = new Lex\Parser();
$template = $parser->parseVariables(file_get_contents('template.lex'), $data);

PHP in Templates

By default PHP is encoded, and not executed. This is for security reasons. However, you may at times want to enable it. To do that simply send true as the fourth parameter to your parse() call.

$parser = new Lex\Parser();
$template = $parser->parse(file_get_contents('template.lex'), $data, $callback, true);

Syntax

General

All Lex code is delimeted by double curly braces ({{ }}). These delimeters were chosen to reduce the chance of conflicts with JavaScript and CSS.

Here is an example of some Lex template code:

Hello, {{name}}

Scope Glue

Scope Glue is/are the character(s) used by Lex to trigger a scope change. A scope change is what happens when, for instance, you are accessing a nested variable inside and array/object, or when scoping a custom callback tag.

By default a dot (.) is used as the Scope Glue, although you can select any character(s).

Setting Scope Glue

$parser->scopeGlue(':');

Whitespace

Whitespace before or after the delimeters is allowed, however, in certain cases, whitespace within the tag is prohibited (explained in the following sections).

Some valid examples:

{{ name }}
{{name }}
{{ name}}
{{  name  }}
{{
  name
}}

Some invalid examples:

{{ na me }}
{ {name} }

Comments

You can add comments to your templates by wrapping the text in {{# #}}.

Example

{{# This will not be parsed or shown in the resulting HTML #}}

{{#
    They can be multi-line too.
#}}

Prevent Parsing

You can prevent the parser from parsing blocks of code by wrapping it in {{ noparse }}{{ /noparse }} tags.

Example

{{ noparse }}
    Hello, {{ name }}!
{{ /noparse }}

Variable Tags

When dealing with variables, you can: access single variables, access deeply nested variables inside arrays/objects, and loop over an array. You can even loop over nested arrays.

Simple Variable Tags

For our basic examples, lets assume you have the following array of variables (sent to the parser):

array(
    'title'     => 'Lex is Awesome!',
    'name'      => 'World',
    'real_name' => array(
        'first' => 'Lex',
        'last'  => 'Luther',
    )
)

Basic Example:

{{# Parsed: Hello, World! #}}
Hello, {{ name }}!

{{# Parsed: <h1>Lex is Awesome!</h1> #}}
<h1>{{ title }}</h1>

{{# Parsed: My real name is Lex Luther!</h1> #}}
My real name is {{ real_name.first }} {{ real_name.last }}

The {{ real_name.first }} and {{ real_name.last }} tags check if real_name exists, then check if first and last respectively exist inside the real_name array/object then returns it.

Looped Variable Tags

Looped Variable tags are just like Simple Variable tags, except they correspond to an array of arrays/objects, which is looped over.

A Looped Variable tag is a closed tag which wraps the looped content. The closing tag must match the opening tag exactly, except it must be prefixed with a forward slash (/). There can be no whitespace between the forward slash and the tag name (whitespace before the forward slash is allowed).

Valid Example:

{{ projects }} Some Content Here {{ /projects }}

Invalid Example:

{{ projects }} Some Content Here {{/ projects }}

The looped content is what is contained between the opening and closing tags. This content is looped through and output for every item in the looped array.

When in a Looped Tag you have access to any sub-variables for the current element in the loop.

In the following example, let's assume you have the following array/object of variables:

array(
    'title'     => 'Current Projects',
    'projects'  => array(
        array(
            'name' => 'Acme Site',
            'assignees' => array(
                array('name' => 'Dan'),
                array('name' => 'Phil'),
            ),
        ),
        array(
            'name' => 'Lex',
            'contributors' => array(
                array('name' => 'Dan'),
                array('name' => 'Ziggy'),
				array('name' => 'Jerel')
            ),
        ),
    ),
)

In the template, we will want to display the title, followed by a list of projects and their assignees.

<h1>{{ title }}</h1>
{{ projects }}
    <h3>{{ name }}</h3>
    <h4>Assignees</h4>
    <ul>
    {{ assignees }}
        <li>{{ name }}</li>
    {{ /assignees }}
    </ul>
{{ /projects }}

As you can see inside each project element we have access to that project's assignees. You can also see that you can loop over sub-values, exactly like you can any other array.

Conditionals

Conditionals in Lex are simple and easy to use. It allows for the standard if, elseif, and else but it also adds unless and elseunless.

The unless and elseunless are the EXACT same as using {{ if ! (expression) }} and {{ elseif ! (expression) }} respectively. They are added as a nicer, more understandable syntax.

All if blocks must be closed with the {{ endif }} tag.

Variables inside of if Conditionals, do not, and should not, use the Tag delimeters (it will cause wierd issues with your output).

A Conditional can contain any Comparison Operators you would do in PHP (==, !=, ===, !==, >, <, <=, >=). You can also use any of the Logical Operators (!, not, ||, &&, and, or).

Examples

{{ if show_name }}
    <p>My name is {{real_name.first}} {{real_name.last}}</p>
{{ endif }}

{{ if user.group == 'admin' }}
    <p>You are an Admin!</p>
{{ elseif user.group == 'user' }}
    <p>You are a normal User.</p>
{{ else }}
    <p>I don't know what you are.</p>
{{ endif }}

{{ if show_real_name }}
    <p>My name is {{real_name.first}} {{real_name.last}}</p>
{{ else }}
    <p>My name is John Doe</p>
{{ endif }}

{{ unless age > 21 }}
    <p>You are to young.</p>
{{ elseunless age < 80 }}
    <p>You are to old...it'll kill ya!</p>
{{ else }}
    <p>Go ahead and drink!</p>
{{ endif }}

The not Operator

The not operator is equivilent to using the ! operator. They are completely interchangable (in-fact not is translated to ! prior to compilation).

Undefined Variables in Conditionals

Undefined variables in conditionals are evaluated to null. This means you can do things like {{ if foo }} and not have to worry if the variable is defined or not.

Checking if a Variable Exists

To check if a variable exists in a conditional, you use the exists keyword.

Examples

{{ if exists foo }}
    Foo Exists
{{ elseif not exists foo }}
    Foo Does Not Exist
{{ endif }}

You can also combine it with other conditions:

{{ if exists foo and foo !== 'bar' }}
    Something here
{{ endif }}

The expression exists foo evaluates to either true or false. Therefore something like this works as well:

{{ if exists foo == false }}
{{ endif }}

Callback Tags in Conditionals

Using a callback tag in a conditional is simple. Use it just like any other variable except for one exception. When you need to provide attributes for the callback tag, you are required to surround the tag with a single set of braces (you can optionally use them for all callback tags).

Note: When using braces inside of a conditional there CANNOT be any whitespace after the opening brace, or before the closing brace of the callback tag within the conditional. Doing so will result in errors.

Examples

{{ if user.logged_in }} {{ endif }}

{{ if user.logged_in and {user.is_group group="admin"} }} {{ endif }}

Callback Tags

Callback tags allow you to have tags with attributes that get sent through a callback. This makes it easy to create a nice plugin system.

Here is an example

{{ template.partial name="navigation" }}

You can also close the tag to make it a Callback Block:

{{ template.partial name="navigation" }}
{{ /template.partial }}

Note that attributes are not required. When no attributes are given, the tag will first be checked to see if it is a data variable, and then execute it as a callback.

{{ template.partial }}

The Callback

The callback can be any valid PHP callable. It is sent to the parse() method as the third parameter:

$parser->parse(file_get_contents('template.lex'), $data, 'my_callback');

The callback must accept the 3 parameters below (in this order):

$name - The name of the callback tag (it would be "template.partial" in the above examples)
$attributes - An associative array of the attributes set
$content - If it the tag is a block tag, it will be the content contained, else a blank string

The callback must also return a string, which will replace the tag in the content.

Example

function my_callback($name, $attributes, $content)
{
    // Do something useful
    return $result;
}

Closing Callback Tags

If a Callback Tag can be used in single or block form, then when using it in it's singular form, it must be closed (just like HTML).

Example

{{ foo.bar.baz }}{{ /foo.bar.baz }}

{{ foo.bar.baz }}
    Content
{{ /foo.bar.baz }}

Self Closing Callback Tags

You can shorten the above by using self-closing tags, just like in HTML. You simply put a / at the end of the tag (there MUST be NO space between the / and the }}).

Example

{{ foo.bar.baz /}}

{{ foo.bar.baz }}
    Content
{{ /foo.bar.baz }}

Recursive Callback Blocks

The recursive callback tag allows you to loop through a child's element with the same output as the main block. It is triggered by using the recursive keyword along with the array key name. The two words must be surrounded by asterisks as shown in the example below.

Example

function my_callback($name, $attributes, $content)
{
	$data = array(
			'url' 		=> 'url_1',
			'title' 	=> 'First Title',
			'children'	=> array(
				array(
					'url' 		=> 'url_2',
					'title'		=> 'Second Title',
					'children' 	=> array(
						array(
							'url' 	=> 'url_3',
							'title'	=> 'Third Title'
						)
					)
				),
				array(
					'url'		=> 'url_4',
					'title'		=> 'Fourth Title',
					'children'	=> array(
						array(
							'url' 	=> 'url_5',
							'title'	=> 'Fifth Title'
						)
					)
				)
			)
	);

	$parser = new Lex\Parser();
	return $parser->parse($content, $data);
}

In the template set it up as shown below. If children is not empty Lex will parse the contents between the {{ navigation }} tags again for each of children's arrays. The resulting text will then be inserted in place of {{ *recursive children* }}. This can be done many levels deep.

<ul>
	{{ navigation }}
		<li><a href="{{ url }}">{{ title }}</a>
			{{ if children }}
				<ul>
					{{ *recursive children* }}
				</ul>
			{{ endif }}
		</li>
	{{ /navigation }}
</ul>

Result

<ul>
	<li><a href="url_1">First Title</a>
		<ul>
			<li><a href="url_2">Second Title</a>
				<ul>
					<li><a href="url_3">Third Title</a></li>
				</ul>
			</li>

			<li><a href="url_4">Fourth Title</a>
				<ul>
					<li><a href="url_5">Fifth Title</a></li>
				</ul>
			</li>
		</ul>
	</li>
</ul>
Comments
  • Not handling invalid variable gracesfully

    Not handling invalid variable gracesfully

    If a variable isn't defined it should return false:

    {{ if navType == 'forum' }} {{ navigation:links group="subnavforum" }} {{ else }} {{ navigation:links group="subnav" }} {{ endif }}

    results in: A PHP Error was encountered Severity: Notice Message: Use of undefined constant navType - assumed 'navType' Filename: Lex/Parser.php(725) : eval()'d code

    See Smarty example: http://www.smarty.net/syntax_comparison

    PHP

     <a href="<?=$bar['zig']?>"><?=$bar['zag']?></a>
     <a href="<?=$bar['zig2']?>"><?=$bar['zag2']?></a> 
     <a href="<?=$bar['zig3']?>"><?=$bar['zag3']?></a> 
    

    There were no rows found.

    Smarty {foreach $foo as $bar} {$bar.zag} {$bar.zag2} {$bar.zag3} {foreachelse} There were no rows found. {/foreach}

    The empty check should be in the foreach or if checks

    opened by andrew13 11
  • Using variables within callbacks arguments

    Using variables within callbacks arguments

    Currently it's impossible to use a variable inside a callback argument. I know it's a lightweight templating engine but could we have the ability to do something like that:

    {{ foo:bar arg="herp/{baz}" }}
    

    Or at least something similar.

    (Link: https://forum.pyrocms.com/discussion/23562/using-variable-in-fileslisting-does-not-work)

    opened by kureuil 7
  • Double tags are greedy

    Double tags are greedy

    This was opened as an issue on PyroCMS with this test code:

    {{ plugin:some_method attribute="value" }}
    
    {{ plugin:some_method attribute="value" }}
        Some Content
    {{ /plugin:some_method }}
    

    Lex matches the outermost tags instead of the innermost so the {{ plugin:some_method attribute="value" }} on line 3 is considered a single tag. Would it be better if Lex was lazy instead?

    opened by jerel 7
  • How to access variables from out loop?

    How to access variables from out loop?

    How can I access variables from out loop inside loop?

    Example:

    $projects = [
                ['name'=> 'Project1']
    ];
    
    $date = '2017-06-21';
    
    {{ projects }}
        <h3>{{ name }}</h3> <small>{{date}}</small>
    {{ /projects }}
    
    opened by LuanMaik 5
  • phpunit dependency?

    phpunit dependency?

    Hey guys, I'm trying to get your phpunit tests running locally. I have a few features we've added i'd love to get test coverage on and possibly send a few pull requests your way, but i'm really having some issues here getting the tests to run.

    The composer package doesn't have any phpunit requirements, so i've added those:

    {
        "name": "fuel/lex",
        "type": "library",
        "description": "A lightweight template parser.",
        "keywords": ["template","parser"],
        "license": "MIT",
        "authors": [
            {
                "name": "Dan Horrigan",
                "email": "[email protected]",
                "role": "Lead Developer"
            }
        ],
        "require": {
            "php": ">=5.3.0"
        },
        "require-dev": {
            "phpunit/phpunit": "3.7.*"
        },
        "autoload": {
            "psr-0": { "Lex": "lib/" }
        }
    }
    

    And then run the composer --dev install, after which running phpunit --configuration phpunit.xml results in:

    Fatal error: Cannot redeclare class Composer\Autoload\ClassLoader in /Users/jack/Projects/lex/vendor/composer/ClassLoader.php on line 44
    

    I can't say for sure if this is something in my own environment or in the Lex test library, but i'm hoping one of you guys might have some insight here.

    opened by jackmcdade 4
  • Conditionals in loop

    Conditionals in loop

    Conditionals in loop doesn't work for me.. Ex:

    $product->cond="yes"

    ....

    {{products}} {{if cond=="yes"}}YES!{{endif}} // not work {{cond}} //work {{/products}}

    opened by peet86 3
  • CodeIgniter Compatibility

    CodeIgniter Compatibility

    Hello,

    Is Lex Parser only working with PyroCMS or can I also use it as a template system for CodeIgniter? Does it need any modifications for this?

    Philip

    opened by philipschilling 1
  • PHPSandbox

    PHPSandbox

    Could this be implemented to reduce the PHP which people can execute? Especially in if statements and the like.

    https://github.com/fieryprophet/php-sandbox

    enhancement 
    opened by philsturgeon 1
  • Hyphens in Variable Names

    Hyphens in Variable Names

    Just wondering if there's any reason why a hyphen is not allowed in a variable name? The parser just ignores a variable with a hyphen because the RegEx doesn't catch it.

    I've implemented a change locally which allows hyphenated variables but was wondering if this was a deliberate choice (i.e. am I letting myself in for a whole world of pain ;-))?

    opened by mtudor 1
  • Lex thinks it needs a close tag?

    Lex thinks it needs a close tag?

    I'm writing a PyroCMS plugin to handle variables. I wrote the bit of "test" code below to test each of the plugin functions but it seems to be jacking up Lex.

    (I plan to make this open source free for Pyro on the modules page once done)

    It seems everything between line 1's {{ var:set name="first" value="John" }}

    and line 3's {{ /var:set }} is being treated as a single tag

    in the example below line 1 is 1 tag and line 3 is a completely separate tag both used to set a variable.

    I guess I can call one set and the other cset (content set) or something?

    1 <p>Set variable named first to John {{ var:set name="first" value="John" }}</p>
    2 <p>Set variable named middle to leon {{ var:middle value="Leon" }}</p>
    3 <p>Set variable named last to Doe {{ var:set name="last" }}Doe{{ /var:set }}</p>
    4 <p>Set variable named age to 41 {{ var:age }}41{{ /var:age }}</p>
    
    5 <p>Variable named first = '{{var:get name="first"}}'</p>
    6 <p>Variable named middle = '{{var:middle}}'</p>
    7 <p>Variable named last = '{{var:get name="last"}}'</p>
    8 <p>Variable named age = '{{var:age}}'</p>
    9 <p>Clear Variable age {{var:clear name="age"}}</p>
    
    10 <p>Variable named age = '{{var:age}}' (Should now be empty)</p>
    
    opened by dmyers2004 1
  • variables in a callback function

    variables in a callback function

    I am stuck in such a situation, i have a tab like

    {{ function op1="option" op2=op op3="option" }}
    

    things work fine when op is set, what should be done when op is not set other than an if-else ??

    opened by hardfire 1
Owner
PyroCMS
Build better Laravel websites and applications faster with Pyro.
PyroCMS
A template abstraction prototype for PHP template engines

Schranz Templating A template abstraction prototype for PHP template engines. This project should help to find a way for a general Template Render Int

Schranz Templating 16 Dec 7, 2022
Twig, the flexible, fast, and secure template language for PHP

Twig, the flexible, fast, and secure template language for PHP Twig is a template language for PHP, released under the new BSD license (code and docum

Twig 7.7k Jan 1, 2023
Smarty is a template engine for PHP, facilitating the separation of presentation (HTML/CSS) from application logic.

Smarty 3 template engine smarty.net Documentation For documentation see www.smarty.net/docs/en/ Requirements Smarty can be run with PHP 5.2 to PHP 7.4

Smarty PHP Template Engine 2.1k Jan 1, 2023
Native PHP template system

Plates Plates is a native PHP template system that's fast, easy to use and easy to extend. It's inspired by the excellent Twig template engine and str

The League of Extraordinary Packages 1.3k Jan 7, 2023
☕ Latte: the intuitive and fast template engine for those who want the most secure PHP sites.

Latte: amazing template engine for PHP Introduction Latte is a template engine for PHP which eases your work and ensures the output is protected again

Nette Foundation 898 Dec 25, 2022
View template engine of PHP extracted from Laravel

Blade 【简体中文】 This is a view templating engine which is extracted from Laravel. It's independent without relying on Laravel's Container or any others.

刘小乐 143 Dec 13, 2022
PHP template engine for native PHP templates

FOIL PHP template engine, for PHP templates. Foil brings all the flexibility and power of modern template engines to native PHP templates. Write simpl

Foil PHP 167 Dec 3, 2022
A complete and fully-functional implementation of the Jade template language for PHP

Tale Jade for PHP Finally a fully-functional, complete and clean port of the Jade language to PHP — Abraham Lincoln The Tale Jade Template Engine brin

Talesoft 91 Dec 27, 2022
Laravel package template

REPLACE Simple and flexible package template. Usage Replace all occurances of REPLACE (case sensitive) with the name of the package namespace. E.g. th

ARCHTECH 56 Aug 15, 2022
A ready-to-use Model View Controller template in PHP

PHP-MVC-Template A ready-to-use Model View Controller template in PHP Use this repo as a template! (Or clone it) Start to configure your MVC file Afte

Loule | Louis 20 Dec 26, 2022
The Templating component provides all the tools needed to build any kind of template system.

Templating Component The Templating component provides all the tools needed to build any kind of template system. It provides an infrastructure to loa

Symfony 999 Dec 25, 2022
Provides a GitHub repository template for a PHP package, using GitHub actions.

php-package-template Installation ?? This is a great place for showing how to install the package, see below: Run $ composer require ergebnis/php-pack

null 280 Dec 27, 2022
The free-to-use template for your Imagehost-website made with PHP, HTML and CSS!

The free-to-use template for your Imagehost-website made with PHP, HTML and CSS! Some information before we start This repo is only code related, to a

Ilian 6 Jul 22, 2022
Document templates Laravel package is intended for creating/managing user editable document template

Document Templates Introduction Document templates Laravel package is intended for creating/managing user editable document templates, with ability to

42coders 139 Dec 15, 2022
The free-to-use template for your Imagehost-website made with PHP, HTML and CSS!

The free-to-use template for your Imagehost-website made with PHP, HTML and CSS! Some information before we start This repo is only code related, to a

Ilian 6 Jul 22, 2022
A SilverStripe Module with template methods to quickly make use of FocusPoint, LazySizes, and Object-fit

LazyFocusFit A SilverStripe module with template methods to quickly make use of FocusPoint, LazySizes and object-fit. Requirements PHP FocusPoint JS/C

Evans Hunt 9 Nov 4, 2022
Astroid Framework - Powerful Joomla Template Framework

Powerful framework for designers and developers to create responsive, fast & robust Joomla based websites and templates.

TemPlaza 42 Dec 14, 2022
⚡️ Simple and fastly template engine for PHP

EasyTpl ⚡️ Simple and fastly template engine for PHP Features It's simple, lightweight and fastly. No learning costs, syntax like PHP template It is s

PHPPkg 19 Dec 9, 2022
Twig Template Engine to Phalcon PHP

Twig Template Engine to Phalcon PHP

Vinicius 4 Oct 7, 2022