{"id":13704088,"url":"https://github.com/pyrocms/lex","last_synced_at":"2025-04-09T10:10:38.617Z","repository":{"id":4228681,"uuid":"5351431","full_name":"pyrocms/lex","owner":"pyrocms","description":"A lightweight template parser used by PyroCMS.","archived":false,"fork":false,"pushed_at":"2014-08-14T18:40:06.000Z","size":360,"stargazers_count":109,"open_issues_count":1,"forks_count":31,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-02T03:56:46.541Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pyrocms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-08-09T05:05:20.000Z","updated_at":"2025-01-29T19:32:23.000Z","dependencies_parsed_at":"2022-08-27T12:31:05.765Z","dependency_job_id":null,"html_url":"https://github.com/pyrocms/lex","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrocms%2Flex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrocms%2Flex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrocms%2Flex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrocms%2Flex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pyrocms","download_url":"https://codeload.github.com/pyrocms/lex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248018061,"owners_count":21034048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T21:01:04.069Z","updated_at":"2025-04-09T10:10:38.582Z","avatar_url":"https://github.com/pyrocms.png","language":"PHP","readme":"Lex\n===\n\n[![Build Status](https://secure.travis-ci.org/pyrocms/lex.png?branch=master)](http://travis-ci.org/pyrocms/lex)\n\nLex is a lightweight template parser.\n\n_Lex is released under the MIT License and is Copyrighted 2011 - 2014 PyroCMS Team._\n\nChange Log\n==========\n\n2.3.2\n-----\n\n* Convert objects with `-\u003etoArray()` at the beginning of the `parser()` method only.\n* As much as we want to say goodbye to PHP 5.3, we brought it back for now.\n\n2.3.1\n-----\n\n* 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 `-\u003etoArray()` method.\n* 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.\n* Dropped support for PHP 5.3\n\n2.3.0\n-----\n\n* Added support for self-closing Callback Tags (e.g. `{{ foo.bar /}}` instead of `{{ foo.bar }}{{ /foo.bar }}`).\n* 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.\n\n2.2.3\n-----\n\n* Fixes issue which caused all callbacks to be processed as single tags, even if they were a block.\n\n2.2.2\n-----\n\n* Fixed #7 - Conditionals inside looped data tags now work as expected.\n\n2.2.1\n-----\n\n* Changed injectNoparse to be static.\n\n2.2.0\n-----\n\n* Fixed a test which was PHP 5.4 only.\n* Added PHPUnit as a composer dev requirement.\n* Added a Lex\\ParsingException class which is thrown when a parsing exception occurs.\n\n2.1.1\n-----\n\n* Fixed an issue where strings returned by callbacks inside a comparison conditional were being processed incorrectly, causing the conditional to always fail.\n\n2.1.0\n-----\n\n* Undefined variables in a conditional now evaluate to NULL, so `{{ if foo }}` now works properly.\n* Added the `exists` keyword.\n* Added the `not` keyword.\n\n2.0.3\n-----\n\n* Fixes composer autoloading.\n* Moved classes into lib folder.\n\n2.0.2\n-----\n\n* Fixed a bug introduced in 2.0.1 where NULL variables were not being displayed.\n\n2.0.1\n-----\n\n* Fixed a bug where variables with a \"falsey\" (e.g. 0, \"0\", -1, etc.) value were not displayed.\n\n2.0.0\n-----\n\n* All code now follows PSR-0, 1 and 2.\n* Lex_Parser has been moved to the `Lex` namespace and renamed to `Parser`.\n* Lex_Autoloader has been removed.  It is now PSR-0 compliant.\n* Added the support for `{{ unless }}` and `{{ elseunless }}`.\n\n\n\nBasic Usage\n===========\n\nUsing Lex\n-------------\n\nLex is a Composer package named `pyrocms/lex`.  To use it, simply add it to the `require` section of you `composer.json` file.\n\n    {\n        \"require\": {\n            \"pyrocms/lex\": \"2.2.*\"\n        }\n    }\n\nAfter adding Lex to your `composer.json` file, simply use the class as normal.\n\n    $parser = new Lex\\Parser();\n\nUsing Lex\n---------\n\nBasic parsing of a file:\n\n    $parser = new Lex\\Parser();\n    $template = $parser-\u003eparse(file_get_contents('template.lex'), $data);\n\nYou can also set the Scope Glue (see \"Scope Glue\" under Syntax below):\n\n    $parser = new Lex\\Parser();\n    $parser-\u003escopeGlue(':');\n    $template = $parser-\u003eparse(file_get_contents('template.lex'), $data);\n\nTo allow noparse extractions to accumulate so they don't get parsed by a later call to the parser set cumulativeNoparse to true:\n\n    $parser = new Lex\\Parser();\n    $parser-\u003ecumulativeNoparse(true);\n    $template = $parser-\u003eparse(file_get_contents('template.lex'), $data);\n\t// Second parse on the same text somewhere else in your app\n\t$template = $parser-\u003eparse($template, $data);\n\t// Now that all parsing is done we inject the contents between the {{ noparse }} tags back into the template text\n\tLex\\Parser::injectNoparse($template);\n\nIf you only want to parse a data array and not worry about callback tags or comments, you can do use the `parseVariables()` method:\n\n    $parser = new Lex\\Parser();\n    $template = $parser-\u003eparseVariables(file_get_contents('template.lex'), $data);\n\n\n### PHP in Templates\n\nBy 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.\n\n    $parser = new Lex\\Parser();\n    $template = $parser-\u003eparse(file_get_contents('template.lex'), $data, $callback, true);\n\nSyntax\n======\n\nGeneral\n-------\n\nAll Lex code is delimeted by double curly braces (`{{ }}`).  These delimeters were chosen to reduce the chance of conflicts with JavaScript and CSS.\n\nHere is an example of some Lex template code:\n\n    Hello, {{name}}\n\n\nScope Glue\n----------\n\nScope 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.\n\nBy default a dot (`.`) is used as the Scope Glue, although you can select any character(s).\n\n`Setting Scope Glue`\n\n    $parser-\u003escopeGlue(':');\n\nWhitespace\n----------\n\nWhitespace before or after the delimeters is allowed, however, in certain cases, whitespace within the tag is prohibited (explained in the following sections).\n\n**Some valid examples:**\n\n    {{ name }}\n    {{name }}\n    {{ name}}\n    {{  name  }}\n    {{\n      name\n    }}\n\n**Some invalid examples:**\n\n    {{ na me }}\n    { {name} }\n\n\nComments\n--------\n\nYou can add comments to your templates by wrapping the text in `{{# #}}`.\n\n**Example**\n\n    {{# This will not be parsed or shown in the resulting HTML #}}\n\n    {{#\n        They can be multi-line too.\n    #}}\n\nPrevent Parsing\n---------------\n\nYou can prevent the parser from parsing blocks of code by wrapping it in `{{ noparse }}{{ /noparse }}` tags.\n\n**Example**\n\n    {{ noparse }}\n        Hello, {{ name }}!\n    {{ /noparse }}\n\nVariable Tags\n-------------\n\nWhen 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.\n\n### Simple Variable Tags\n\nFor our basic examples, lets assume you have the following array of variables (sent to the parser):\n\n    array(\n        'title'     =\u003e 'Lex is Awesome!',\n        'name'      =\u003e 'World',\n        'real_name' =\u003e array(\n            'first' =\u003e 'Lex',\n            'last'  =\u003e 'Luther',\n        )\n    )\n\n**Basic Example:**\n\n\t{{# Parsed: Hello, World! #}}\n    Hello, {{ name }}!\n\n\t{{# Parsed: \u003ch1\u003eLex is Awesome!\u003c/h1\u003e #}}\n    \u003ch1\u003e{{ title }}\u003c/h1\u003e\n\n\t{{# Parsed: My real name is Lex Luther!\u003c/h1\u003e #}}\n    My real name is {{ real_name.first }} {{ real_name.last }}\n\nThe `{{ 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.\n\n### Looped Variable Tags\n\nLooped Variable tags are just like Simple Variable tags, except they correspond to an array of arrays/objects, which is looped over.\n\nA 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).\n\n**Valid Example:**\n\n    {{ projects }} Some Content Here {{ /projects }}\n\n**Invalid Example:**\n\n    {{ projects }} Some Content Here {{/ projects }}\n\nThe 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.\n\nWhen in a Looped Tag you have access to any sub-variables for the current element in the loop.\n\nIn the following example, let's assume you have the following array/object of variables:\n\n    array(\n        'title'     =\u003e 'Current Projects',\n        'projects'  =\u003e array(\n            array(\n                'name' =\u003e 'Acme Site',\n                'assignees' =\u003e array(\n                    array('name' =\u003e 'Dan'),\n                    array('name' =\u003e 'Phil'),\n                ),\n            ),\n            array(\n                'name' =\u003e 'Lex',\n                'contributors' =\u003e array(\n                    array('name' =\u003e 'Dan'),\n                    array('name' =\u003e 'Ziggy'),\n\t\t\t\t\tarray('name' =\u003e 'Jerel')\n                ),\n            ),\n        ),\n    )\n\nIn the template, we will want to display the title, followed by a list of projects and their assignees.\n\n    \u003ch1\u003e{{ title }}\u003c/h1\u003e\n    {{ projects }}\n        \u003ch3\u003e{{ name }}\u003c/h3\u003e\n        \u003ch4\u003eAssignees\u003c/h4\u003e\n        \u003cul\u003e\n        {{ assignees }}\n            \u003cli\u003e{{ name }}\u003c/li\u003e\n        {{ /assignees }}\n        \u003c/ul\u003e\n    {{ /projects }}\n\nAs 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.\n\nConditionals\n-------------\n\nConditionals in Lex are simple and easy to use.  It allows for the standard `if`, `elseif`, and `else` but it also adds `unless` and `elseunless`.\n\nThe `unless` and `elseunless` are the EXACT same as using `{{ if ! (expression) }}` and `{{ elseif ! (expression) }}` respectively.  They are added as a nicer, more understandable syntax.\n\nAll `if` blocks must be closed with the `{{ endif }}` tag.\n\nVariables inside of if Conditionals, do not, and should not, use the Tag delimeters (it will cause wierd issues with your output).\n\nA Conditional can contain any Comparison Operators you would do in PHP (`==`, `!=`, `===`, `!==`, `\u003e`, `\u003c`, `\u003c=`, `\u003e=`).  You can also use any of the Logical Operators (`!`, `not`, `||`, `\u0026\u0026`, `and`, `or`).\n\n**Examples**\n\n    {{ if show_name }}\n        \u003cp\u003eMy name is {{real_name.first}} {{real_name.last}}\u003c/p\u003e\n    {{ endif }}\n\n    {{ if user.group == 'admin' }}\n        \u003cp\u003eYou are an Admin!\u003c/p\u003e\n    {{ elseif user.group == 'user' }}\n        \u003cp\u003eYou are a normal User.\u003c/p\u003e\n    {{ else }}\n        \u003cp\u003eI don't know what you are.\u003c/p\u003e\n    {{ endif }}\n\n    {{ if show_real_name }}\n        \u003cp\u003eMy name is {{real_name.first}} {{real_name.last}}\u003c/p\u003e\n    {{ else }}\n        \u003cp\u003eMy name is John Doe\u003c/p\u003e\n    {{ endif }}\n\n    {{ unless age \u003e 21 }}\n        \u003cp\u003eYou are to young.\u003c/p\u003e\n    {{ elseunless age \u003c 80 }}\n        \u003cp\u003eYou are to old...it'll kill ya!\u003c/p\u003e\n    {{ else }}\n        \u003cp\u003eGo ahead and drink!\u003c/p\u003e\n    {{ endif }}\n\n### The `not` Operator\n\nThe `not` operator is equivilent to using the `!` operator.  They are completely interchangable (in-fact `not` is translated to `!` prior to compilation).\n\n### Undefined Variables in Conditionals\n\nUndefined 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.\n\n### Checking if a Variable Exists\n\nTo check if a variable exists in a conditional, you use the `exists` keyword.\n\n**Examples**\n\n    {{ if exists foo }}\n        Foo Exists\n    {{ elseif not exists foo }}\n        Foo Does Not Exist\n    {{ endif }}\n\nYou can also combine it with other conditions:\n\n    {{ if exists foo and foo !== 'bar' }}\n        Something here\n    {{ endif }}\n\nThe expression `exists foo` evaluates to either `true` or `false`.  Therefore something like this works as well:\n\n    {{ if exists foo == false }}\n    {{ endif }}\n\n### Callback Tags in Conditionals\n\nUsing 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).\n\n**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.**\n\n**Examples**\n\n    {{ if user.logged_in }} {{ endif }}\n\n    {{ if user.logged_in and {user.is_group group=\"admin\"} }} {{ endif }}\n\n\nCallback Tags\n-------------\n\nCallback tags allow you to have tags with attributes that get sent through a callback.  This makes it easy to create a nice plugin system.\n\nHere is an example\n\n    {{ template.partial name=\"navigation\" }}\n\nYou can also close the tag to make it a **Callback Block**:\n\n    {{ template.partial name=\"navigation\" }}\n    {{ /template.partial }}\n\nNote 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.\n\n    {{ template.partial }}\n\n### The Callback\n\nThe callback can be any valid PHP callable.  It is sent to the `parse()` method as the third parameter:\n\n    $parser-\u003eparse(file_get_contents('template.lex'), $data, 'my_callback');\n\nThe callback must accept the 3 parameters below (in this order):\n\n    $name - The name of the callback tag (it would be \"template.partial\" in the above examples)\n    $attributes - An associative array of the attributes set\n    $content - If it the tag is a block tag, it will be the content contained, else a blank string\n\nThe callback must also return a string, which will replace the tag in the content.\n\n**Example**\n\n    function my_callback($name, $attributes, $content)\n    {\n        // Do something useful\n        return $result;\n    }\n\n### Closing Callback Tags\n\nIf 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).\n\n**Example**\n\n    {{ foo.bar.baz }}{{ /foo.bar.baz }}\n\n    {{ foo.bar.baz }}\n        Content\n    {{ /foo.bar.baz }}\n\n#### Self Closing Callback Tags\n\nYou 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 `}}`).\n\n**Example**\n\n    {{ foo.bar.baz /}}\n\n    {{ foo.bar.baz }}\n        Content\n    {{ /foo.bar.baz }}\n\n\nRecursive Callback Blocks\n-------------\n\nThe recursive callback tag allows you to loop through a child's element with the same output as the main block. It is triggered\nby using the ***recursive*** keyword along with the array key name. The two words must be surrounded by asterisks as shown in the example below.\n\n**Example**\n\n\tfunction my_callback($name, $attributes, $content)\n\t{\n\t\t$data = array(\n\t\t\t\t'url' \t\t=\u003e 'url_1',\n\t\t\t\t'title' \t=\u003e 'First Title',\n\t\t\t\t'children'\t=\u003e array(\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'url' \t\t=\u003e 'url_2',\n\t\t\t\t\t\t'title'\t\t=\u003e 'Second Title',\n\t\t\t\t\t\t'children' \t=\u003e array(\n\t\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t\t'url' \t=\u003e 'url_3',\n\t\t\t\t\t\t\t\t'title'\t=\u003e 'Third Title'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'url'\t\t=\u003e 'url_4',\n\t\t\t\t\t\t'title'\t\t=\u003e 'Fourth Title',\n\t\t\t\t\t\t'children'\t=\u003e array(\n\t\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t\t'url' \t=\u003e 'url_5',\n\t\t\t\t\t\t\t\t'title'\t=\u003e 'Fifth Title'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t);\n\n\t\t$parser = new Lex\\Parser();\n\t\treturn $parser-\u003eparse($content, $data);\n\t}\n\n\nIn the template set it up as shown below. If `children` is not empty Lex will\nparse the contents between the `{{ navigation }}` tags again for each of `children`'s arrays.\nThe resulting text will then be inserted in place of `{{ *recursive children* }}`. This can be done many levels deep.\n\n\t\u003cul\u003e\n\t\t{{ navigation }}\n\t\t\t\u003cli\u003e\u003ca href=\"{{ url }}\"\u003e{{ title }}\u003c/a\u003e\n\t\t\t\t{{ if children }}\n\t\t\t\t\t\u003cul\u003e\n\t\t\t\t\t\t{{ *recursive children* }}\n\t\t\t\t\t\u003c/ul\u003e\n\t\t\t\t{{ endif }}\n\t\t\t\u003c/li\u003e\n\t\t{{ /navigation }}\n\t\u003c/ul\u003e\n\n\n**Result**\n\n\t\u003cul\u003e\n\t\t\u003cli\u003e\u003ca href=\"url_1\"\u003eFirst Title\u003c/a\u003e\n\t\t\t\u003cul\u003e\n\t\t\t\t\u003cli\u003e\u003ca href=\"url_2\"\u003eSecond Title\u003c/a\u003e\n\t\t\t\t\t\u003cul\u003e\n\t\t\t\t\t\t\u003cli\u003e\u003ca href=\"url_3\"\u003eThird Title\u003c/a\u003e\u003c/li\u003e\n\t\t\t\t\t\u003c/ul\u003e\n\t\t\t\t\u003c/li\u003e\n\n\t\t\t\t\u003cli\u003e\u003ca href=\"url_4\"\u003eFourth Title\u003c/a\u003e\n\t\t\t\t\t\u003cul\u003e\n\t\t\t\t\t\t\u003cli\u003e\u003ca href=\"url_5\"\u003eFifth Title\u003c/a\u003e\u003c/li\u003e\n\t\t\t\t\t\u003c/ul\u003e\n\t\t\t\t\u003c/li\u003e\n\t\t\t\u003c/ul\u003e\n\t\t\u003c/li\u003e\n\t\u003c/ul\u003e\n","funding_links":[],"categories":["模板","模板 Templating","Templating","目录","模板引擎( Templating )"],"sub_categories":["模板 Templating"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrocms%2Flex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpyrocms%2Flex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrocms%2Flex/lists"}