Файловый менеджер - Редактировать - /var/www/html/zordius.zip
Ðазад
PK ! ,>�3m m lightncandy/README.mdnu �Iw�� LightnCandy =========== ⚡🍭 An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ). Travis CI status: [](https://travis-ci.org/zordius/lightncandy) [](https://travis-ci.org/zordius/HandlebarsTest) Scrutinizer CI status: [](https://scrutinizer-ci.com/g/zordius/lightncandy/) Package on packagist: [](https://packagist.org/packages/zordius/lightncandy) [](https://github.com/zordius/lightncandy/blob/master/LICENSE.md) [](https://packagist.org/packages/zordius/lightncandy) Features -------- * Logicless template: mustache ( http://mustache.github.com/ ) or handlebars ( http://handlebarsjs.com/ ) . * Compile template to **pure PHP** code. Examples: * <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/001-simple-vars.tmpl">Template A</a> generated <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/001-simple-vars.php">PHP A</a> * <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/016-hb-eachthis.tmpl">Template B</a> generated <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/016-hb-eachthis.php">PHP B</a> * **FAST!** * Runs 2~7 times faster than <a href="https://github.com/bobthecow/mustache.php">mustache.php</a> (Justin Hileman/bobthecow implementation). * Runs 2~7 times faster than <a href="https://github.com/dingram/mustache-php">mustache-php</a> (Dave Ingram implementation). * Runs 10~50 times faster than <a href="https://github.com/XaminProject/handlebars.php">handlebars.php</a>. * Detail performance test reports can be found <a href="https://github.com/zordius/HandlebarsTest">here</a>, go http://zordius.github.io/HandlebarsTest/ to see charts. * **SMALL!** all PHP files in 189K * **ROBUST!** * 100% supports <a href="https://github.com/mustache/spec">mustache spec v1.1.3</a>. For the optional lambda module, supports 4 of 10 specs. * Supports almost all <a href="https://github.com/jbboehr/handlebars-spec">handlebars.js spec</a> * Output <a href="https://github.com/zordius/HandlebarsTest/blob/master/FEATURES.md">SAME</a> with <a href="https://github.com/wycats/handlebars.js">handlebars.js</a> * **FLEXIBLE!** * Lot of <a href="#compile-options">options</a> to change features and behaviors. * Context generation * Analyze used features from your template (execute `LightnCandy::getContext()` to get it) . * Debug * <a href="#template-debugging">Generate debug version template</a> * Find out missing data when rendering template. * Generate visually debug template. * Standalone Template * The compiled PHP code can run without any PHP library. You do not need to include LightnCandy when execute rendering function. Installation ------------ Use Composer ( https://getcomposer.org/ ) to install LightnCandy: ``` composer require zordius/lightncandy:dev-master ``` **UPGRADE NOTICE** * Please check <a href="HISTORY.md">HISTORY.md</a> for versions history. * Please check <a href="UPGRADE.md">UPGRADE.md</a> for upgrade notice. Documents --------- * <a href="https://zordius.github.io/HandlebarsCookbook/9000-quickstart.html">Quick Start</a> Compile Options --------------- You can apply more options by running `LightnCandy::compile($template, $options)`: ```php LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_STANDALONEPHP )); ``` Default is to compile the template as PHP, which can be run as fast as possible (flags = <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_BESTPERFORMANCE.html">FLAG_BESTPERFORMANCE</a>). **Error Handling** * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_ERROR_LOG.html">FLAG_ERROR_LOG</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_ERROR_EXCEPTION.html">FLAG_ERROR_EXCEPTION</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_ERROR_SKIPPARTIAL.html">FLAG_ERROR_SKIPPARTIAL</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_RENDER_DEBUG.html">FLAG_RENDER_DEBUG</a> **JavaScript Compatibility** * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_JSTRUE.html">FLAG_JSTRUE</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_JSOBJECT.html">FLAG_JSOBJECT</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_JSLENGTH.html">FLAG_JSLENGTH</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_JS.html">FLAG_JS</a> **Mustache Compatibility** * `FLAG_MUSTACHELOOKUP` : align recursive lookup up behaviors with mustache specification. And, the rendering performance will be worse. * `FLAG_MUSTACHELAMBDA` : support simple lambda logic as mustache specification. And, the rendering performance will be worse. * `FLAG_NOHBHELPERS` : Do not compile handlebars.js builtin helpers. With this option, `{{#with}}`, `{{#if}}`, `{{#unless}}`, `{{#each}}` means normal section, and `{{#with foo}}`, `{{#if foo}}`, `{{#unless foo}}`, `{{#each foo}}` will cause compile error. * `FLAG_MUSTACHESECTION` : align section context behaviors with mustache.js. * `FLAG_MUSTACHE` : support all mustache specification but performance drop, same with `FLAG_ERROR_SKIPPARTIAL` + `FLAG_MUSTACHELOOKUP` + `FLAG_MUSTACHELAMBDA` + `FLAG_NOHBHELPERS` + `FLAG_RUNTIMEPARTIAL` + `FLAG_JSTRUE` + `FLAG_JSOBJECT`. **Handlebars Compatibility** * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_THIS.html">FLAG_THIS</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_PARENT.html">FLAG_PARENT</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_HBESCAPE.html">FLAG_HBESCAPE</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_ADVARNAME.html">FLAG_ADVARNAME</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_NAMEDARG.html">FLAG_NAMEDARG</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_SLASH.html">FLAG_SLASH</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_ELSE.html">FLAG_ELSE</a> * `FLAG_RAWBLOCK`: support `{{{{raw_block}}}}any_char_or_{{foo}}_as_raw_string{{{{/raw_block}}}}`. * `FLAG_HANDLEBARSLAMBDA` : support lambda logic as handlebars.js specification. And, the rendering performance will be worse. * `FLAG_SPVARS` : support special variables include @root, @index, @key, @first, @last. Otherwise, compile these variable names with default parsing logic. * `FLAG_HANDLEBARS` : support most handlebars extensions and also keep performance good, same with `FLAG_THIS` + `FLAG_PARENT` + `FLAG_HBESCAPE` + `FLAG_ADVARNAME` + `FLAG_NAMEDARG` + `FLAG_SPVARS` + `FLAG_SLASH` + `FLAG_ELSE` + `FLAG_RAWBLOCK`. * `FLAG_HANDLEBARSJS` : support most handlebars.js + javascript behaviors and also keep performance good, same with `FLAG_JS` + `FLAG_HANDLEBARS`. * `FLAG_HANDLEBARSJS_FULL` : enable all supported handlebars.js behaviors but performance drop, same with `FLAG_HANDLEBARSJS` + `FLAG_INSTANCE` + `FLAG_RUNTIMEPARTIAL` + `FLAG_MUSTACHELOOKUP` + `FLAG_HANDLEBARSLAMBDA`. **Handlebars Options** * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_NOESCAPE.html">FLAG_NOESCAPE</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_PARTIALNEWCONTEXT.html">FLAG_PARTIALNEWCONTEXT</a> * `FLAG_IGNORESTANDALONE` : prevent standalone detection on `{{#foo}}`, `{{/foo}}` or `{{^}}`, the behavior is same with handlebars.js ignoreStandalone compile time option. * `FLAG_STRINGPARAMS` : pass variable name as string to helpers, the behavior is same with handlebars.js stringParams compile time option. * `FLAG_KNOWNHELPERSONLY`: Only pass current context to lambda, the behavior is same with handlebars.js knownHelpersOnly compile time option. * `FLAG_PREVENTINDENT` : align partial indent behavior with mustache specification. This is same with handlebars.js preventIndent copmile time option. **PHP** * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_STANDALONEPHP.html">FLAG_STANDALONEPHP</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_EXTHELPER.html">FLAG_EXTHELPER</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_RUNTIMEPARTIAL.html">FLAG_RUNTIMEPARTIAL</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_PROPERTY.html">FLAG_PROPERTY</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_METHOD.html">FLAG_METHOD</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_INSTANCE.html">FLAG_INSTANCE</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_ECHO.html">FLAG_ECHO</a> * <a href="https://zordius.github.io/HandlebarsCookbook/LC-FLAG_BESTPERFORMANCE.html">FLAG_BESTPERFORMANCE</a> Partial Support --------------- * <a href="https://zordius.github.io/HandlebarsCookbook/0011-partial.html">Example of compile time partial</a> * <a href="https://zordius.github.io/HandlebarsCookbook/0024-partialcontext.html">Example of partial context changing</a> * <a href="https://zordius.github.io/HandlebarsCookbook/0028-dynamicpartial.html">use dynamic partial</a> * <a href="https://zordius.github.io/HandlebarsCookbook/9902-lcop-partialresolver.html">The partialresolver option</a> Custom Helper ------------- * <a href="https://zordius.github.io/HandlebarsCookbook/9001-customhelper.html">Custom Helpers in LighnCandy</a> * <a href="https://zordius.github.io/HandlebarsCookbook/9002-helperoptions.html">The $options Object</a> * <a href="https://zordius.github.io/HandlebarsCookbook/9003-helperescaping.html">Use SafeString</a> * <a href="https://zordius.github.io/HandlebarsCookbook/9901-lcop-helperresolver.html">The helperresolver option</a> Custom Helper Examples ---------------------- **#mywith (context change)** * LightnCandy ```php // LightnCandy sample, #mywith works same with #with $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => array( 'mywith' => function ($context, $options) { return $options['fn']($context); } ) )); ``` * Handlebars.js ```javascript // Handlebars.js sample, #mywith works same with #with Handlebars.registerHelper('mywith', function(context, options) { return options.fn(context); }); ``` **#myeach (context change)** * LightnCandy ```php // LightnCandy sample, #myeach works same with #each $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => array( 'myeach' => function ($context, $options) { $ret = ''; foreach ($context as $cx) { $ret .= $options['fn']($cx); } return $ret; } ) )); ``` * Handlebars.js ```javascript // Handlebars.js sample, #myeach works same with #each Handlebars.registerHelper('myeach', function(context, options) { var ret = '', i, j = context.length; for (i = 0; i < j; i++) { ret = ret + options.fn(context[i]); } return ret; }); ``` **#myif (no context change)** * LightnCandy ```php // LightnCandy sample, #myif works same with #if $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => array( 'myif' => function ($conditional, $options) { if ($conditional) { return $options['fn'](); } else { return $options['inverse'](); } } ) )); ``` * Handlebars.js ```javascript // Handlebars.js sample, #myif works same with #if Handlebars.registerHelper('myif', function(conditional, options) { if (conditional) { return options.fn(this); } else { return options.inverse(this); } }); ``` You can use `isset($options['fn'])` to detect your custom helper is a block or not; you can also use `isset($options['inverse'])` to detect the existence of `{{else}}`. **Data variables and context** You can get special data variables from `$options['data']`. Using `$options['_this']` to receive current context. ```php $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => array( 'getRoot' => function ($options) { print_r($options['_this']); // dump current context return $options['data']['root']; // same as {{@root}} } ) )); ``` * Handlebars.js ```javascript Handlebars.registerHelper('getRoot', function(options) { console.log(this); // dump current context return options.data.root; // same as {{@root}} }); ``` **Private variables** You can inject private variables into inner block when you execute child block with second parameter. The example code showed similar behavior with `{{#each}}` which sets index for child block and can be accessed with `{{@index}}`. * LightnCandy ```php $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => array( 'list' => function ($context, $options) { $out = ''; $data = $options['data']; foreach ($context as $idx => $cx) { $data['index'] = $idx; $out .= $options['fn']($cx, array('data' => $data)); } return $out; } ) )); ``` * Handlebars.js ```javascript Handlebars.registerHelper('list', function(context, options) { var out = ''; var data = options.data ? Handlebars.createFrame(options.data) : undefined; for (var i=0; i<context.length; i++) { if (data) { data.index = i; } out += options.fn(context[i], {data: data}); } return out; }); ``` Change Delimiters ----------------- You may change delimiters from `{{` and `}}` to other strings. In the template, you can use `{{=<% %>=}}` to change delimiters to `<%` and `%>` , but the change will not affect included partials. If you want to change default delimiters for a template and all included partials, you may `compile()` it with `delimiters` option: ```php LightnCandy::compile('I wanna use <% foo %> as delimiters!', array( 'delimiters' => array('<%', '%>') )); ``` Template Debugging ------------------ When template error happened, LightnCandy::compile() will return false. You may compile with `FLAG_ERROR_LOG` to see more error message, or compile with `FLAG_ERROR_EXCEPTION` to catch the exception. You may generate debug version of templates with `FLAG_RENDER_DEBUG` when compile() . The debug template contained more debug information and slower (TBD: performance result) , you may pass extra LightnCandy\Runtime options into render function to know more rendering error (missing data). For example: ```php $template = "Hello! {{name}} is {{gender}}. Test1: {{@root.name}} Test2: {{@root.gender}} Test3: {{../test3}} Test4: {{../../test4}} Test5: {{../../.}} Test6: {{../../[test'6]}} {{#each .}} each Value: {{.}} {{/each}} {{#.}} section Value: {{.}} {{/.}} {{#if .}}IF OK!{{/if}} {{#unless .}}Unless not OK!{{/unless}} "; // compile to debug version $phpStr = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_RENDER_DEBUG | LightnCandy::FLAG_HANDLEBARSJS )); // Save the compiled PHP code into a php file file_put_contents('render.php', '<?php ' . $phpStr . '?>'); // Get the render function from the php file $renderer = include('render.php'); // error_log() when missing data: // LightnCandy\Runtime: [gender] is not exist // LightnCandy\Runtime: ../[test] is not exist $renderer(array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_ERROR_LOG)); // Output visual debug template with ANSI color: echo $renderer(array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_TAGS_ANSI)); // Output debug template with HTML comments: echo $renderer(array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_TAGS_HTML)); ``` The ANSI output will be: <a href="tests/example_debug.php"><img src="https://github.com/zordius/lightncandy/raw/master/example_debug.png"/></a> Here are the list of LightnCandy\Runtime debug options for render function: * `DEBUG_ERROR_LOG` : error_log() when missing required data * `DEBUG_ERROR_EXCEPTION` : throw exception when missing required data * `DEBUG_TAGS` : turn the return value of render function into normalized mustache tags * `DEBUG_TAGS_ANSI` : turn the return value of render function into normalized mustache tags with ANSI color * `DEBUG_TAGS_HTML` : turn the return value of render function into normalized mustache tags with HTML comments Preprocess Partials ------------------- If you want to do extra process before the partial be compiled, you may use `prepartial` when `compile()`. For example, this sample adds HTML comments to identify the partial by the name: ```php $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'prepartial' => function ($context, $template, $name) { return "<!-- partial start: $name -->$template<!-- partial end: $name -->"; } )); ``` You may also extend <a href="https://zordius.github.io/lightncandy/class-LightnCandy.Partial.html">LightnCandy\Partial</a> by override the <a href="https://zordius.github.io/lightncandy/class-LightnCandy.Partial.html#_prePartial">prePartial()</a> static method to turn your preprocess into a built-in feature. Customize Render Function ------------------------- If you want to do extra tasks inside render function or add more comment, you may use `renderex` when `compile()` . For example, this sample embed the compile time comment into the template: ```php $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'renderex' => '// Compiled at ' . date('Y-m-d h:i:s') )); ``` Your render function will be: ```php function ($in) { $cx = array(...); // compiled at 1999-12-31 00:00:00 return ..... } ``` Please make sure the passed in `renderex` is valid PHP, LightnCandy will not check it. Customize Rendering Runtime Class --------------------------------- If you want to extend `LightnCandy\Runtime` class and replace the default runtime library, you may use `runtime` when `compile()` . For example, this sample will generate render function based on your extended `MyRunTime`: ```php // Customized runtime library to debug {{{foo}}} class MyRunTime extends LightnCandy\Runtime { public static function raw($cx, $v) { return '[[DEBUG:raw()=>' . var_export($v, true) . ']]'; } } // Use MyRunTime as runtime library $php = LightnCandy::compile($template, array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'runtime' => 'MyRunTime' )); ``` Please make sure `MyRunTime` exists when compile() or rendering based on your `FLAG_STANDALONEPHP` . Unsupported Feature ------------------- * `{{foo/bar}}` style variable name, it is deprecated in official handlebars.js document, please use this style: `{{foo.bar}}`. Suggested Handlebars Template Practices --------------------------------------- * Prevent to use `{{#with}}` . I think `{{path.to.val}}` is more readable then `{{#with path.to}}{{val}}{{/with}}`; when using `{{#with}}` you will confusing on scope changing. `{{#with}}` only save you very little time when you access many variables under same path, but cost you a lot time when you need to understand then maintain a template. * use `{{{val}}}` when you do not require HTML escaped output on the value. It is better performance, too. * Prevent to use custom helper if you want to reuse your template in different language. Or, you may need to implement different versions of helper in different languages. * For best performance, you should only use 'compile on demand' pattern when you are in development stage. Before you go to production, you can `LightnCandy::compile()` on all your templates, save all generated PHP codes, and deploy these generated files (You may need to maintain a build process for this) . **DO NOT COMPILE ON PRODUCTION** , it also a best practice for security. Adding cache for 'compile on demand' is not the best solution. If you want to build some library or framework based on LightnCandy, think about this scenario. * Recompile your templates when you upgrade LightnCandy every time. * Persistant ESCAPING practice of `{` or `}` for both handlebars and lightncandy: * If you want to display atomic `}}` , you can just use it without any trick. EX: `{{foo}} }}` * If you want to display `}` just after any handlebars token, you can use this: `{{#with "}"}}{{.}}{{/with}}` . EX: `{{foo}}{{#with "}"}}{{.}}{{/with}}` * If you want to display atomic `{` , you can just use it without any trick. EX: `{ and {{foo}}`. * If you want to display `{{` , you can use `{{#with "{{"}}{{.}}{{/with}}`. EX: `{{#with "{{"}}{{.}}{{/with}}{{foo}}` Detail Feature list ------------------- Go http://handlebarsjs.com/ to see more feature description about handlebars.js. All features align with it. * Exact same CR/LF behavior with handlebars.js * Exact same CR/LF bahavior with mustache spec * Exact same 'true' or 'false' output with handlebars.js (require `FLAG_JSTRUE`) * Exact same '[object Object]' output or join(',' array) output with handlebars.js (require `FLAG_JSOBJECT`) * Can place heading/tailing space, tab, CR/LF inside `{{ var }}` or `{{{ var }}}` * Indent behavior of the partial same with mustache spec * Recursive variable lookup to parent context behavior same with mustache spec (require `FLAG_MUSTACHELOOKUP`) * `{{{value}}}` or `{{&value}}` : raw variable * true as 'true' (require `FLAG_JSTRUE`) * false as 'false' (require `FLAG_TRUE`) * `{{value}}` : HTML escaped variable * true as 'true' (require `FLAG_JSTRUE`) * false as 'false' (require `FLAG_JSTRUE`) * `{{{path.to.value}}}` : dot notation, raw * `{{path.to.value}}` : dot notation, HTML escaped * `{{.}}` : current context, HTML escaped * `{{{.}}}` : current context, raw * `{{this}}` : current context, HTML escaped (require `FLAG_THIS`) * `{{{this}}}` : current context, raw (require `FLAG_THIS`) * `{{#value}}` : section * false, undefined and null will skip the section * true will run the section with original scope * All others will run the section with new scope (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...) * `{{/value}}` : end section * `{{^value}}` : inverted section * false, undefined and null will run the section with original scope * All others will skip the section (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...) * `{{! comment}}` : comment * `{{!-- comment or {{ or }} --}}` : extended comment that can contain }} or {{ . * `{{=<% %>=}}` : set delimiter to custom string , the custom string can not contain `=` . Check http://mustache.github.io/mustache.5.html for more example. * `{{#each var}}` : each loop * `{{#each}}` : each loop on {{.}} * `{{/each}}` : end loop * `{{#each bar as |foo|}}` : echo loop on bar and set the value as foo. (require `FLAG_ADVARNAME`) * `{{#each bar as |foo moo|}}` : echo loop on bar, set the value as foo, set the index as moo. (require `FLAG_ADVARNAME`) * `{{#if var}}` : run if logic with original scope (null, false, empty Array and '' will skip this block) * `{{#if foo includeZero=true}}` : result as true when foo === 0 (require `FLAG_NAMEDARG`) * `{{/if}}` : end if * `{{else}}` or `{{^}}` : run else logic, should between `{{#if var}}` and `{{/if}}` ; or between `{{#unless var}}` and `{{/unless}}`; or between `{{#foo}}` and `{{/foo}}`; or between `{{#each var}}` and `{{/each}}`; or between `{{#with var}}` and `{{/with}}`. (require `FLAG_ELSE`) * `{{#if foo}} ... {{else if bar}} ... {{/if}}` : chained if else blocks * `{{#unless var}}` : run unless logic with original scope (null, false, empty Array and '' will render this block) * `{{#unless foo}} ... {{else if bar}} ... {{/unless}}` : chained unless else blocks * `{{#unless foo}} ... {{else unless bar}} ... {{/unless}}` : chained unless else blocks * `{{#foo}} ... {{else bar}} ... {{/foo}}` : custom helper chained else blocks * `{{#with var}}` : change context scope. If the var is false or an empty array, skip included section. * `{{#with bar as |foo|}}` : change context to bar and set the value as foo. (require `FLAG_ADVARNAME`) * `{{lookup foo bar}}` : lookup foo by value of bar as key. * `{{../var}}` : parent template scope. (require `FLAG_PARENT`) * `{{>file}}` : partial; include another template inside a template. * `{{>file foo}}` : partial with new context (require `FLAG_RUNTIMEPARTIAL`) * `{{>file foo bar=another}}` : partial with new context which mixed with followed key value (require `FLAG_RUNTIMEPARTIAL`) * `{{>(helper) foo}}` : include dynamic partial by name provided from a helper (require `FLAG_RUNTIMEPARTIAL`) * `{{@index}}` : references to current index in a `{{#each}}` loop on an array. (require `FLAG_SPVARS`) * `{{@key}}` : references to current key in a `{{#each}}` loop on an object. (require `FLAG_SPVARS`) * `{{@root}}` : references to root context. (require `FLAG_SPVARS`) * `{{@first}}` : true when looping at first item. (require `FLAG_SPVARS`) * `{{@last}}` : true when looping at last item. (require `FLAG_SPVARS`) * `{{@root.path.to.value}}` : references to root context then follow the path. (require `FLAG_SPVARS`) * `{{@../index}}` : access to parent loop index. (require `FLAG_SPVARS` and `FLAG_PARENT`) * `{{@../key}}` : access to parent loop key. (require `FLAG_SPVARS` and `FLAG_PARENT`) * `{{foo.[ba.r].[#spec].0.ok}}` : references to $CurrentConext['foo']['ba.r']['#spec'][0]['ok'] . (require `FLAG_ADVARNAME`) * `{{~any_valid_tag}}` : Space control, remove all previous spacing (includes CR/LF, tab, space; stop on any none spacing character) * `{{any_valid_tag~}}` : Space control, remove all next spacing (includes CR/LF, tab, space; stop on any none spacing character) * `{{{helper var}}}` : Execute custom helper then render the result * `{{helper var}}` : Execute custom helper then render the HTML escaped result * `{{helper "str"}}` or `{{helper 'str'}}` : Execute custom helper with string arguments (require `FLAG_ADVARNAME`) * `{{helper 123 null true false undefined}}` : Pass number, true, false, null or undefined into helper * `{{helper name1=var name2=var2}}` : Execute custom helper with named arguments (require `FLAG_NAMEDARG`) * `{{#helper ...}}...{{/helper}}` : Execute block custom helper * `{{helper (helper2 foo) bar}}` : Execute custom helpers as subexpression (require `FLAG_ADVARNAME`) * `{{{{raw_block}}}} {{will_not_parsed}} {{{{/raw_block}}}}` : Raw block (require `FLAG_RAWBLOCK`) * `{{#> foo}}block{{/foo}}` : Partial block, provide `foo` partial default content (require `FLAG_RUNTIMEPARTIAL`) * `{{#> @partial-block}}` : access partial block content inside a partial * `{{#*inline "partial_name"}}...{{/inline}}` : Inline partial, provide a partial and overwrite the original one. * `{{log foo}}` : output value to stderr for debug. Developer Notes --------------- Please read <a href=".github/CONTRIBUTING.md">CONTRIBUTING.md</a> for development environment setup. Framework Integration --------------------- - [Slim 3.0.x](https://github.com/endel/slim-lightncandy-view) - [Laravel 4](https://github.com/samwalshnz/lightncandy-l4) - [Laravel 5](https://github.com/ProAI/laravel-handlebars) - [yii2](https://github.com/kfreiman/yii2-lightncandy) - [Symfony3](https://packagist.org/packages/jays-de/handlebars-bundle) Tools ----- - CLI: https://github.com/PXLbros/LightnCandy-CLI PK ! �U��\ �\ lightncandy/HISTORY.mdnu �Iw�� HISTORY ======= 1.2.7 current master, not released * align with handlebars.js 4.1.2 1.2.6 https://github.com/zordius/lightncandy/releases/v1.2.6 * align with handlebars.js 4.1.2 * 207d424c4c fix for PHP 8.0 code generator issue 1.2.5 https://github.com/zordius/lightncandy/releases/v1.2.5 * align with handlebars.js 4.1.2 * 0ae02b0131 support ArrayAccess and __call() * ade2d444c7 fix for PHP 7.4 implode() warning * c9f02aa6e4 fix for PHP 7.4 implode() warning * 096bf4c2ae remove PHP 7.1 tests (stop supporting/verifying) * c77aaa7a27 fix for PHP 7.4 warning access int as array 1.2.4 https://github.com/zordius/lightncandy/releases/v1.2.4 * align with handlebars.js 4.1.1 * 67b91587f8 fix {{#if}} {{else}} {{/if}} bug when them are inside {{#*inline}}...{{/inline}} * e232e13e12 fix {{else}} compile bug when them are inside {{#*inline}}...{{/inline}} 1.2.3 https://github.com/zordius/lightncandy/releases/v1.2.3 * align with handlebars.js 4.0.11 * f429712406 fix partial block standalone tag detection issue * 4f1ca85a08 fix for {{#each foo as |bar|}} when the value is not an array * ca892c4514 fix for PHP 7.2 count(null) issue and StringObject not found issue 1.2.2 https://github.com/zordius/lightncandy/releases/v1.2.2 * align with handlebars.js 4.0.11 * 6ef2efd8d9 fix LightnCandy::compilePartial() error when there is `'` in the partial * 658244f864 better error message when a=b found without FLAG_NAMEDARG option * a752abd855 **BREAK CHANGE** remove FLAG_SPACECTL option because it always enabled and useless * 9615b44920 fix partial block stand alone detection issue 1.2.1 https://github.com/zordius/lightncandy/releases/v1.2.1 * align with handlebars.js 4.0.10 * 15201d7300 fix `{{foo (bar "moo (1 2)")}}` parsing issue 1.2.0 https://github.com/zordius/lightncandy/releases/v1.2.0 * align with handlebars.js 4.0.10 * 046de72460 reduce is_array() check when context is not changed * d217900d5b **BREAK CHANGE** * fix {{#sec}} context switch behavior for mustache.js compatibility * new `FLAG_MUSTACHESECTION` to align {{#sec}} context switch behavior with mustache.js * now `FLAG_MUSTACHE` includes `FLAG_MUSTACHESECTION` 1.1.0 https://github.com/zordius/lightncandy/releases/v1.1.0 * align with handlebars.js 4.0.6 * 0557429c07 fix {{lookup . "foo"}} parsing issue * 6939eebef8 fix {{foo a=(foo a=(bar))}} parsing issue * 59eba28d8a fix error message when compile {{each foo as |bar|}} * 3164c82047 **BREAK CHANGE** block custom helper is compiled as `hbbch()` now 1.0.3 https://github.com/zordius/lightncandy/releases/v1.0.3 * align with handlebars.js 4.0.6 * b25ea6b1de support FLAG_JSLENGTH when FLAG_METHOD 1.0.2 https://github.com/zordius/lightncandy/releases/v1.0.2 * align with handlebars.js 4.0.6 * e9108cdb52 {{#if}}{{else if}}{{/if}} will not cause next {{else}} validation error now 1.0.1 https://github.com/zordius/lightncandy/releases/v1.0.1 * align with handlebars.js 4.0.6 * cb94f98149 fix helpers in partial are not collected bug * bcbadd785d lookup inside subexpression returns untouched value now * 18e114a683 {{foo.bar}} will never be resolved as custom helper now 1.0.0 https://github.com/zordius/lightncandy/releases/v1.0.0 * align with handlebars.js 4.0.6 * b2d9eab03a fix generated PHP code error when FLAG_RUNTIMEPARTIAL is on * 510b4611bb fix `{{lookup . foo}}` issue * e629941f8f fix `Cannot use lexical variable $sp as a parameter name` issue in PHP 7.1 * 84c94665d8 export SafeString static properties when FLAG_STANDALONEPHP is enabled * 5e9f6d0bc7 fix FLAG_STANDALONEPHP mode LS Class bug * 7ec3dac944 refine safestring compile option * 1f10501c01 fix {{#with .}} context behavior * 426291d7ad support recursive {{@partial-block}} v0.95 https://github.com/zordius/lightncandy/releases/v0.95 * align with handlebars.js 4.0.5 * 71abc9853e fix `{{> (lookup foo bar)}}` issue (allow builtin helpers in subexpression) * c321e477b2 fix comment inside partial block issue * 6e98a13a55 fix `@partial-block` id generation issue v0.94 https://github.com/zordius/lightncandy/releases/v0.94 * align with handlebars.js 4.0.5 * 7082bd3276 refine helper function exporter * dcda1202cf now can compile custom helpers without implementation when FLAG_EXTHELPER is on * 2eb2ae8e8d supports `{{else with foo}}` , `{{else each bar}}`, `{{else myHelper}}` now * cd8d96befa fix {{foo.bar}} lookup PHP warning * 0effc46896 fix compile bug when override built-in helpers v0.93 https://github.com/zordius/lightncandy/releases/v0.93 * align with handlebars.js 4.0.5 * a9fb08c3e8 speed up `{{@var}}` lookup, check parent type when `{{../var}}` * c7040ecc76 **BREAK CHANGE** * new FLAG_JSLENGTH to support {{array.length}} lookup * now FLAG_JS includes FLAG_JSLENGTH * now FLAG_HANDLEBARSJS includes FLAG_JSLENGTH * 8f88c409c9 fix compiler bug for none FLAG_ELSE cases * a7617d9ff1 now default options.inverse provided even when no `{{else}}` in template * 45e0b600f7 refine unclosed partial block error message * 73ac95e882 allow spacing between `{{` and `#` now v0.92 https://github.com/zordius/lightncandy/releases/v0.92 * align with handlebars.js 4.0.5 * c9811d9c3a Detect `{{..foo}}` syntax error * 2e77514841 fix `[fo o]="1 2 3"` parsing bug * 52b072e246 fix compiler error when a block customhelper inside `{{else if}}` * e30edc4302 fix `{{#with .}}` logic when input is an empty array v0.91 https://github.com/zordius/lightncandy/releases/v0.91 * align with handlebars.js 4.0.5 * 93323508ce now FLAG_BESTPERFORMANCE also includes FLAG_STANDALONEPHP. * 4deffaf89d prevent warning message when FLAG_ERROR_SKIPPARTIAL used. * fbc04f065b **BREAK CHANGE** remove `basedir` and `fileext` option. * 5c7c651985 new `partialresolver` option. * 771eedfda4 fix compile error when `{{#if}}..{{else if}}..no_else_here..{{/if}}` * 68134c627c do not use SafeString when no Runtime::enc() included * 6cda56c552 Now FLAG_STANDALONEPHP do not require \LightnCandy\SafeString class * 2dd671592e fix the [fo o]=123 parsing bug * 993e65ef8e fix @partial-block duplication bug * 2c9044e1d7 new `helperresolver` option. v0.90 https://github.com/zordius/lightncandy/releases/v0.90 * align with handlebars.js 4.0.4 * 47e1b28847 performance improvement: now `{{ ... }}` escaping only takes 70% time. * ff776f1dff add new options `delimiters` to change default delimiters. * 8942155d20 **BREAK CHANGE** remove FLAG_BARE , the generated PHP code will not includes `<?php` and `?>` now. * 9dab19e658 support `{{else if ...}}` and `{{else unless ...}}` now. * 58a23f3ef6 support `{{log ...}}` now. * d5c32c4028 **BREAK CHANGE** remove option: helpers, blockhelpers. * dab79506cc support SafeString now. * dab79506cc **BREAK CHANGE** rename option: hbhelpers into helpers. * a1f699efa7 support render time partial function. v0.89 https://github.com/zordius/lightncandy/releases/v0.89 * align with handlebars.js 4.0.4 * use newer handlebars spec: https://github.com/jbboehr/handlebars-spec * 50028d36a7 **BREAK CHANGE** remove FLAG_MUSTACHESP * e32079fe08 fix standalone detection on single `{{.}}` or `{{this}}` * 83caaec2c0 support `{{#if foo includeZero=true}}` * 6c636a8857 support literal references * 3d7a7d81a7 **BREAK CHANGE** remove FLAG_MUSTACHEPAIN * 3d7a7d81a7 new flag FLAG_PREVENTINDENT to stop auto indent on partial. * 0d3a92a52e new flag FLAG_HANDLEBARSJS_FULL to enable all handlebars features with performance drop * c76b9c6fc0 **BREAK CHANGE** now FLAG_MUSTACHE also includes FLAG_RUNTIMEPARTIAL * 28be0377ea new flag FLAG_MUSTACHELAMBDA to support simple case of mustache lambda * 37ba20c234 **BREAK CHANGE** rename LCRun3 to LCRun4 for interface changed, old none standalone templates will error with newer version * 8f062e4ef1 fix for nested subexpression parsing bug * b3704e78c4 new flag FLAG_HANDLEBARSLAMBDA to support handlebars lambda * 4d4f4d5b57 **BREAK CHANGE** start to use namespace, support psr-4 autoloader by composer * rename LightnCandy to LightnCandy\LightnCandy * rename LCRun4 to LightnCandy\Runtime * rename `lcrun` option to `runtime` * b818c2411e split LightnCandy methods into different classes * 67a4518460 Refactoring Validator and Compiler done * e1362e7779 Usage counting feature fixed * 4e21ff3f11 **BREAK CHANGE** remove FLAG_WITH * fde6859ae7 new flag FLAG_NOHBHELPERS to remove all handlebars.js builtin helpers * 82e4221919 **BREAK CHANGE** now FLAG_MUSTACHE also includes FLAG_NOHBHELPERS * b71afbead3 **BREAK CHANGE** rename FLAG_JSQUOTE to FLAG_HBESCAPE * 90c2bbf16d **BREAK CHANGE** now {{#if}} and {{#unless}} context behavior align with handlebars.js 4.0.4 * c65e380877 **BREAK CHANGE** now flag FLAG_PREVENTINDENT behavior align with handlebars.js options.preventIndent * f85001d225 fix standalone detection when space control {{~}} used * 6d1e390c5d now hbhelpers context change behavior align with handlebars.js 4.0.4 * 0d1f00196f **BREAK CHANGE** now render function interface align with handlebars.js 4.0.4 * 44741ac197 supports {{lookup foo bar}} now * 50eae060c5 new flag FLAG_PARTIALNEWCONTEXT to create new empty context for every partial * e118a08e6b **BREAK CHANGE** rename FLAG_STANDALONE to FLAG_STANDALONEPHP * 3994014ca4 support {{#with bar as |foo|}} * fc2f9643c7 support {{#each foo as |value index|}} * 83a173e575 support block params for hbhelpers * 2a262f671b new flag FLAG_STRINGPARAMS to support handlebars.js options.stringParams * dcc84c3118 support lambda arguments * edef496b25 maintain options.contexts for custom helpers now * 8e94d5d6ba support partial block: {{#> foo}}block{{/foo}} * 4347a78b3e support partial block: {{> @partial-block}} * f4df6d722f support inline partial: {{#*inline "partial_name"}}...{{/inline}} * 71130e69e0 fix partial block + inline partial parsing bugs v0.23 https://github.com/zordius/lightncandy/releases/v0.23 * align with handlebars.js 3.0.3 * b194f37430 support `{{{{rawblock}}}} ... {{{{/rawblock}}}}` when FLAG_RAWBLOCK enabled * 927741a07c add `prePartial()` static method and `prepartial` compile option for extendibility * f9f41277d7 support private variable injection from handlebars custom block helpers * 850dcd7082 fix `{{!-- --}}` bug when it inside a partial * edb486ac87 fix support for nested raw block v0.22 https://github.com/zordius/lightncandy/releases/v0.22 * align with handlebars.js 3.0.3 * 1d1e8829cb fix `{{foo bar=(tee_taa "hoo")}}` parsing issue * 9bd994ee94 fix JavaScript function in runtime partial be changed bug * a514e4652e fix `{{#foo}}` issue when foo is an empty ArrayObject v0.21 https://github.com/zordius/lightncandy/releases/v0.21 * align with handlebars.js 3.0.3 * 9f24268d57 support FLAG_BARE to remove PHP start/end tags * 60d5a46c55 handle object/propery merge when deal with partial * d0130bf7e5 support undefined `{{helper undefined}}` * 8d228606f7 support `lcrun` to use customized render library when compile() * d0bad115f0 remove tmp PHP file when prepare() now * d84bbb4519 support keeping tmp PHP file when prepare() * ee833ae2f8 fix syntax validator bug on `{{helper "foo[]"}}` * 30b891ab28 fix syntax validator bug on `{{helper 'foo[]'}}` * 1867f1cc37 now count subexpression usage correctly * 78ef9b8a89 new syntax validator on handlebars variable name v0.20 https://github.com/zordius/lightncandy/releases/v0.20 * align with handlebars.js 3.0.0 * 3d9a557af9 fix `{{foo (bar ../abc)}}` compile bug * 7dc16ac255 refine custom helper error detection logic * 72d32dc299 fix subexpression parsing bug inside `{{#each}}` * d1f1b93130 support context access inside a hbhelper by `$options['_this']` v0.19 https://github.com/zordius/lightncandy/releases/v0.19 * align with handlebars.js 3.0.0 * 5703851e49 fix `{{foo bar=['abc=123']}}` parsing bug * 7b4e36a1e3 fix `{{foo bar=["abc=123"]}}` parsing bug * c710c8349b fix `{{foo bar=(helper a b c)}}` parsing bug * 4bda1c6f41 fix subexpression+builtin block helper (EX: `{{#if (foo bar)}}`) parsing bug * 6fdba10fc6 fix `{{foo ( bar) or " car" or ' cat' or [ cage]}}` pasing bug * 0cd5f2d5e2 fix indent issue when custom helper inside a partial * 296ea89267 support dynamic partial `{{> (foo)}}` * f491d04bd5 fix `{{../foo}}` look up inside root scope issue * 38fba8a5a5 fix scope issue for hbhelpers * a24a0473e2 change internal variable structure and fix for `{{number}}` * 7ae8289b7e fix escape in double quoted string bug * 90adb5531b fix `{{#if 0.0}}` logic to behave as false * 004a6ddffe fix `{{../foo}}` double usage count bug * 9d55f12c5a fix subexpression parsing bug when line change inside it * fe1cb4987a **BREAK CHANGE** remove FLAG_MUSTACHESEC v0.18 https://github.com/zordius/lightncandy/releases/v0.18 * align with handlebars.js 2.0.0 * 7bcce4c1a7 support `{{@last}}` for `{{#each}}` on both object and array * b0c44c3b40 remove ending \n in lightncandy.php * e130875d5a support single quoted string input: `{{foo 'bar'}}` * c603aa39d8 support `renderex` to extend anything in render function * f063e5302c now render function debug constants works well in standalone mode * 53f6a6816d fix parsing bug when there is a `=` inside single quoted string * 2f16c0c393 now really autoload when installed with composer * c4da1f576c supports `{{^myHelper}}` v0.17 https://github.com/zordius/lightncandy/releases/v0.17 * align with handlebars.js 2.0.0 * 3b48a0acf7 fix parsing bug when FLAG_NOESCAPE enabled * 5c774b1b08 fix hbhelpers response error with options['fn'] when FLAG_BESTPERFORMANCE enabled * c60fe70bdb fix hbhelpers response error with options['inverse'] when FLAG_BESTPERFORMANCE enabled * e19b3e3426 provide options['root'] and options['_parent'] to hbhelpers * d8a288e83b refine variable parsing logic to support `{{@../index}}`, `{{@../key}}`, etc. v0.16 https://github.com/zordius/lightncandy/releases/v0.16 * align with handlebars.js 2.0.0 * 4f036aff62 better error message for named arguments * 0b462a387b support `{{#with var}}` ... `{{else}}` ... `{{/with}}` * 4ca624f651 fix 1 ANSI code error * 01ea3e9f42 support instances with PHP __call magic funciton * 38059036a7 support `{{#foo}}` or `{{#each foo}}` on PHP Traversable instance * 366f5ec0ac add FLAG_MUSTACHESP and FLAG_MUSTACHEPAIN into FLAG_HANDLEBARS and FLAG_HANDLEBARSJS now * b61d7b4a81 align with handlebars.js standalone tags behavior * b211e1742e now render false as 'false' * 655a2485be fix bug for `{{helper "==="}}` * bb58669162 support FLAG_NOESCAPE v0.15 https://github.com/zordius/lightncandy/releases/v0.15 * align with handlebars.js 2.0.0 * 4c750806e8 fix for `\` in template * 12ab6626d6 support escape. `\{{foo}}` will be rendered as is. ( handlebars spec , require FLAG_SLASH ) * 876bd44d9c escape ` to &#x60; ( require FLAG_JSQUOTE ) * f1f388ed79 support `{{^}}` as `{{else}}` ( require FLAG_ELSE ) * d5e17204b6 support `{{#each}}` == `{{#each .}}` now * 742126b440 fix `{{>foo/bar}}` partial not found bug * d62c261ff9 support numbers as helper input `{{helper 0.1 -1.2}}` * d40c76b84f support escape in string arguments `{{helper "test \" double quote"}}` * ecb57a2348 fix for missing partial in partial bug * 1adad5dbfa fix `{{#with}}` error when FLAG_WITH not used * ffd5e35c2d fix error when rendering array value as `{{.}}` without FLAG_JSOBJECT * bd4987adbd support changing context on partial `{{>foo bar}}` ( require FLAG_RUNTIMEPARTIAL ) * f5decaa7e3 support name sarguments on partial `{{>foo bar name=tee}}` . fix `{{..}}` bug * c20bb36457 support `partials` in options * e8779dbe8c change default `basedir` hehavior, stop partial files lookup when do not prodive `basedir` in options * c4e3401fe4 fix `{{>"test"}}` or `{{>[test]}}` or `{{>1234}}` bug * e59f62ea9b fix seciton behavior when input is object, and add one new flag: FLAG_MUSTACHESEC * 80eaf8e007 use static::method not self::method for subclass * 0bad5c8f20 fix usedFeature generation bugs v0.14 https://github.com/zordius/lightncandy/releases/v0.14 * align with handlebars.js 2.0.0-alpha.4 * fa6225f278 support boolen value in named arguments for cusotm helper * 160743e1c8 better error message when unmatch `{{/foo}}` tag detected * d9a9416907 support `{{&foo}}` * 8797485cfa fix `{{^foo}}` logic when foo is empty list * 523b1373c4 fix handlebars custom helper interface * a744a2d522 fix bad syntax when FLAG_RENDER_DEBUG + helpers * 0044f7bd10 change FLAG_THIS behavoir * b5b0739b68 support recursive context lookup now ( mustache spec , require FLAG_MUSTACHELOOKUP ) * 096c241fce support standalone tag detection now ( mustache spec , require FLAG_MUSTACHESP ) * cea46c9a67 support `{{=<% %>=}}` to set delimiter * 131696af11 support subexpression `{{helper (helper2 foo) bar}}` * 5184d41be6 support runtime/recursive partial ( require FLAG_RUNTIMEPARTIAL ) * 6408917f76 support partial indent ( mustache spec , require FLAG_MUSTACHEPAIN ) v0.13 https://github.com/zordius/lightncandy/releases/v0.13 * align with handlebars.js 2.0.0-alpha.4 * e5a8fe3833 fix issue #46 ( error with `{{this.foo.bar}}` ) * ea131512f9 fix issue #44 ( error with some helper inline function PHP code syntax ) * 522591a0c6 fix issue #49 ( error with some helper user function PHP code syntax ) * c4f7e1eaac support `{{foo.bar}} lookup on instance foo then property/method bar ( flagd FLAG_PROPERTY or FLAG_METHOD required ) * 0f4c0daa4b stop simulate Javascript output for array when pass input to custom helpers * 22d07e5f0f **BREAK CHANGE** BIG CHANGE of custom helper interface v0.12 https://github.com/zordius/lightncandy/releases/v0.12 * align with handlebars.js 2.0.0-alpha.2 * 64db34cf65 support `{{@first}}` and `{{@last}}` * bfa1fbef97 add new flag FLAG_SPVARS * 10a4623dc1 **BREAK CHANGE** remove json schema support * 240d9fa290 only export used LCRun2 functions when compile() with FLAG_STANDALONE now * 3fa897c98c **BREAK CHANGE** rename LCRun2 to LCRun3 for interface changed, old none standalone templates will error with newer version * e0838c7418 now can output debug template map with ANSI color * 80dbeab63d fix php warning when compile with custom helper or block custom helper * 8ce6268b64 support Handlebars.js style custom helper v0.11 https://github.com/zordius/lightncandy/releases/v0.11 * align with handlebars.js 2.0.0-alpha.2 * a275d52c97 use php array, remove val() * 8834914c2a only export used custom helper into render function now * eb6d82d871 refine option flag consts * fc437295ed refine comments for phpdoc * fbf116c3e2 fix for tailing ; after helper functions * f47a2d5014 fix for wrong param when new Exception * 94e71ebcbd add isset() check for input value * a826b8a1ab support `{{else}}` in `{{#each}}` now * 25dac11bb7 support `{{!-- comments --}}` now (this handlebars.js extension allow `}}` to be placed inside a comment) * e142b6e116 support `{{@root}}` or `{{@root.foo.bar}}` now * 58c8d84aa2 custom helper can return extra flag to change html encoded behavior now v0.10 https://github.com/zordius/lightncandy/releases/v0.10 * align with handlebars.js 2.0.0-alpha.1 * 4c9f681080 file name changed: lightncandy.inc => lightncandy.php * e3de01081c some minor fix for json schema * 1feec458c7 new variable handling logic, save variable name parsing time when render() . rendering performance improved 10~30%! * 3fa897c98c **BREAK CHANGE** rename LCRun to LCRun2 for interface changed, old none standalone templates will error with newer version * 43a6d33717 fix for `{{../}}` php warning message * 9189ebc1e4 now auto push documents from Travis CI * e077d0b631 support named arguments for custom helpers `{{helper name=value}}` * 2331b6fe55 support block custom helpers * 4fedaa25f7 support number value as named arguments * 6a91ab93d2 fix for default options and php warnings * fc157fde62 fix for doblue quoted arguments (issue #15) v0.9 https://github.com/zordius/lightncandy/releases/v0.9 * align with handlebars.js 1.3 * **STOP PHP 5.3.x testing and support** * a55f2dd067 support both `{{@index}}` and `{{@key}}` when `{{#each an_object}}` * e59f931ea7 add FLAG_JSQUOTE support * 92b3cf58af report more than 1 error when compile() * 93cc121bcf test for wrong variable name format in test/error.php * 41c1b431b4 support advanced variable naming `{{foo.[bar].10}}` now * 15ce1a00a8 add FLAG_EXTHELPER option * f51337bde2 support space control `{{~sometag}}` or `{{sometag~}}` * fe3d67802e add FLAG_SPACECTL option * 920fbb3039 support custom helper * 07ae71a1bf migrate into Travis CI * ddd3335ff6 support "some string" argument * 20f6c888d7 html encode after custom helper executed * 10a2f45fdc add test generator * ccd1d3ddc2 **BREAK CHANGE** migrate to Scrutinizer, change file name LightnCandy.inc to LightnCandy.php * 5ac8ad8d04 now is a Composer package v0.8 https://github.com/zordius/lightncandy/releases/v0.8 * align with handlebars.js 1.0.12 * aaec049 fix partial in partial not works bug * 52706cc fix for `{{#var}}` and `{{^var}}` , now when var === 0 means true * 4f7f816 support `{{@key}}` and `{{@index}}` in `{{#each var}}` * 63aef2a prevent array_diff_key() PHP warning when `{{#each}}` on none array value * 10f3d73 add more is_array() check when `{{#each}}` and `{{#var}}` * 367247b fix `{{#if}}` and `{{#unless}}` when value is an empty array * c76c0bb returns null if var is not exist in a template [contributed by dtelyukh@github.com] * d18bb6d add FLAG_ECHO support * aec2b2b fix `{{#if}}` and `{{#unless}}` when value is an empty string * 8924604 fix variable output when false in an array * e82c324 fix for ifv and ifvar logic difference * 1e38e47 better logic on var name checking. now support `{{0}}` in the loop, but it is not handlebars.js standard v0.7 https://github.com/zordius/lightncandy/releases/v0.7 * align with handlebarsjs 1.0.11 * add HISTORY.md * 777304c change compile format to include in val, isec, ifvar * 55de127 support `{{../}}` in `{{#each}}` * 57e90af fix parent levels detection bug * 96bb4d7 fix bugs for `{{#.}}` and `{{#this}}` * f4217d1 add ifv and unl 2 new methods for LCRun * 3f1014c fix `{{#this}}` and `{{#.}}` bug when used with `{{../var}}` * cbf0b28 fix `{{#if}}` logic error when using `{{../}}` * 2b20ef8 fix `{{#with}}` + `{{../}}` bug * 540cd44 now support FLAG_STANDALONE * 67ac5ff support `{{>partial}}` * 98c7bb1 detect unclosed token now v0.6 https://github.com/zordius/lightncandy/releases/v0.6 * align with handlebarsjs 1.0.11 * 45ac3b6 fix #with bug when var is false * 1a46c2c minor #with logic fix. update document * fdc753b fix #each and section logic for 018-hb-withwith-006 * e6cc95a add FLAG_PARENT, detect template error when scan() * 1980691 make new LCRun::val() method to normal path.val logic * 110d24f `{{#if path.var}}` bug fixed * d6ae2e6 fix `{{#with path.val}}` when input value is null * 71cf074 fix for 020-hb-doteach testcase v0.5 https://github.com/zordius/lightncandy/releases/v0.5 * align with handlebarsjs 1.0.7 * 955aadf fix #each bug when input is a hash PK ! v5�� � lightncandy/UPGRADE.mdnu �Iw�� Upgrade Notice ============== * Standalone templates compiled by older LightnCandy can be executed safe when you upgrade to any new version of LightnCandy. * Recompile your none standalone templates when you upgrade LightnCandy. Version v1.0.0 -------------- * Support PHP 7.1 Version v0.91 ------------- * Option basedir removed. Please use the new partialresolver option to handle partial files. * Option fileext removed. Please use the new partialresolver option to handle partial files. Version v0.90 ------------- * Option FLAG_BARE removed. * When you save your compiled PHP code into a file, you need to add `<?php ` and `?>` by yourself. * Remove $option['helpers'] and $option['blockhelpers'] * Rename $option['hbhelpers'] into $option['helpers'] Version v0.89 ------------- * Option FLAG_MUSTACHESP removed. * Option FLAG_MUSTACHEPAIN removed. * Option FLAG_WITH removed. * Option FLAG_MUSTACHE includes FLAG_RUNTIMEPARTIAL now. * Option FLAG_MUSTACHE includes FLAG_NOHBHELPERS now. * Option FLAG_JSQUOTE is changed to FLAG_HBESCAPE * Option FLAG_STANDALONE is changed to FLAG_STANDALONEPHP * Option `lcrun` is changed to `runtime` * generated render function interface changed, aligned with handlebars.js now * LightnCandy be refactored into many sub classes, you can not just use curl to install it now. * Due to big change of rendering function: sec() and inv(), the rendering supporting class `LCRun3` is renamed to `LightnCandy\Runtime`. If you compile templates as none standalone PHP code by LightnCandy v0.23 or before, you should compile these templates again. Or, you may run into `Class 'LCRun3' not found` error when you execute these old rendering functions. Version v0.19 ------------- * Option FLAG_MUSTACHESEC removed, no need to use this flag anymore. Version v0.13 ------------- * The interface of custom helpers was changed from v0.13 . if you use this feature you may need to modify your custom helper functions. Version v0.12 ------------- * LightnCandy::getJsonSchema() removed * jsonSchema generation feature removed * Due to big change of render() debugging, the rendering supporting class `LCRun2` is renamed to `LCRun3`. If you compile templates as none standalone PHP code by LightnCandy v0.11 or before, you should compile these templates again. Or, you may run into `Class 'LCRun2' not found` error when you execute these old rendering functions. Version v0.10 ------------ * Due to big change of variable name handling, the rendering supporting class `LCRun` is renamed to `LCRun2`. If you compile templates as none standalone PHP code by LightnCandy v0.9 or before, you should compile these templates again. Or, you may run into `Class 'LCRun' not found` error when you execute these old rendering functions. PK ! �4b�D D lightncandy/LICENSE.mdnu �Iw�� MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK ! s�t� � lightncandy/src/loader.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * PHP loader for LightnCandy * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ require_once(__DIR__ . '/Flags.php'); require_once(__DIR__ . '/Context.php'); require_once(__DIR__ . '/Token.php'); require_once(__DIR__ . '/Encoder.php'); require_once(__DIR__ . '/SafeString.php'); require_once(__DIR__ . '/Parser.php'); require_once(__DIR__ . '/Expression.php'); require_once(__DIR__ . '/Validator.php'); require_once(__DIR__ . '/Partial.php'); require_once(__DIR__ . '/Exporter.php'); require_once(__DIR__ . '/Runtime.php'); require_once(__DIR__ . '/Compiler.php'); require_once(__DIR__ . '/LightnCandy.php'); PK ! �z� � lightncandy/src/Partial.phpnu �Iw�� <?php /* Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to keep LightnCandy partial methods * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy Partial handler */ class Partial { public static $TMP_JS_FUNCTION_STR = "!!\aFuNcTiOn\a!!"; /** * Include all partials when using dynamic partials */ public static function handleDynamic(&$context) { if ($context['usedFeature']['dynpartial'] == 0) { return; } foreach ($context['partials'] as $name => $code) { static::read($context, $name); } } /** * Read partial file content as string and store in context * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $name partial name * * @return string|null $code compiled PHP code when success */ public static function read(&$context, $name) { $isPB = ($name === '@partial-block'); $context['usedFeature']['partial']++; if (isset($context['usedPartial'][$name])) { return; } $cnt = static::resolve($context, $name); if ($cnt !== null) { $context['usedPartial'][$name] = SafeString::escapeTemplate($cnt); return static::compileDynamic($context, $name); } if (!$context['flags']['skippartial'] && !$isPB) { $context['error'][] = "Can not find partial for '$name', you should provide partials or partialresolver in options"; } } /** * preprocess partial template before it be stored into context * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $tmpl partial template * @param string $name partial name * * @return string|null $content processed partial template * * @expect 'hey' when input array('prepartial' => false), 'hey', 'haha' * @expect 'haha-hoho' when input array('prepartial' => function ($cx, $tmpl, $name) {return "$name-$tmpl";}), 'hoho', 'haha' */ protected static function prePartial(&$context, $tmpl, &$name) { return $context['prepartial'] ? $context['prepartial']($context, $tmpl, $name) : $tmpl; } /** * resolve partial, return the partial content * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $name partial name * * @return string|null $content partial content */ public static function resolve(&$context, &$name) { if ($name === '@partial-block') { $name = "@partial-block{$context['usedFeature']['pblock']}"; } if (isset($context['partials'][$name])) { return static::prePartial($context, $context['partials'][$name], $name); } return static::resolver($context, $name); } /** * use partialresolver to resolve partial, return the partial content * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $name partial name * * @return string|null $content partial content */ public static function resolver(&$context, &$name) { if ($context['partialresolver']) { $cnt = $context['partialresolver']($context, $name); return static::prePartial($context, $cnt, $name); } } /** * compile a partial to static embed PHP code * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $name partial name * * @return string|null $code PHP code string */ public static function compileStatic(&$context, $name) { // Check for recursive partial if (!$context['flags']['runpart']) { $context['partialStack'][] = $name; $diff = count($context['partialStack']) - count(array_unique($context['partialStack'])); if ($diff) { $context['error'][] = 'I found recursive partial includes as the path: ' . implode(' -> ', $context['partialStack']) . '! You should fix your template or compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag.'; } } $code = Compiler::compileTemplate($context, preg_replace('/^/m', $context['tokens']['partialind'], $context['usedPartial'][$name])); if (!$context['flags']['runpart']) { array_pop($context['partialStack']); } return $code; } /** * compile partial as closure, stored in context * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $name partial name * * @return string|null $code compiled PHP code when success */ public static function compileDynamic(&$context, $name) { if (!$context['flags']['runpart']) { return; } $func = static::compile($context, $context['usedPartial'][$name], $name); if (!isset($context['partialCode'][$name]) && $func) { $context['partialCode'][$name] = "'$name' => $func"; } return $func; } /** * compile a template into a closure function * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $template template string * @param string|integer $name partial name or 0 * * @return string $code compiled PHP code */ public static function compile(&$context, $template, $name = 0) { if ((end($context['partialStack']) === $name) && (substr($name, 0, 14) === '@partial-block')) { return; } $tmpContext = $context; $tmpContext['inlinepartial'] = array(); $tmpContext['partialblock'] = array(); if ($name !== 0) { $tmpContext['partialStack'][] = $name; } $code = Compiler::compileTemplate($tmpContext, str_replace('function', static::$TMP_JS_FUNCTION_STR, $template)); Context::merge($context, $tmpContext); if (!$context['flags']['noind']) { $sp = ', $sp'; $code = preg_replace('/^/m', "'{$context['ops']['seperator']}\$sp{$context['ops']['seperator']}'", $code); // callbacks inside partial should be aware of $sp $code = preg_replace('/\bfunction\s*\(([^\(]*?)\)\s*{/', 'function(\\1)use($sp){', $code); $code = preg_replace('/function\(\$cx, \$in, \$sp\)use\(\$sp\){/', 'function($cx, $in)use($sp){', $code); } else { $sp = ''; } $code = str_replace(static::$TMP_JS_FUNCTION_STR, 'function', $code); return "function (\$cx, \$in{$sp}) {{$context['ops']['array_check']}{$context['ops']['op_start']}'$code'{$context['ops']['op_end']}}"; } } PK ! E/�� � lightncandy/src/Encoder.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to keep LightnCandy Encoder * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy class to encode. */ class Encoder { /** * Get string value * * @param array<string,array|string|integer> $cx render time context * @param array<array|string|integer>|string|integer|null $v value to be output * @param integer $ex 1 to return untouched value, default is 0 * * @return array<array|string|integer>|string|integer|null The raw value of the specified variable * * @expect true when input array('flags' => array('jstrue' => 0, 'mustlam' => 0, 'lambda' => 0)), true * @expect 'true' when input array('flags' => array('jstrue' => 1)), true * @expect '' when input array('flags' => array('jstrue' => 0, 'mustlam' => 0, 'lambda' => 0)), false * @expect 'false' when input array('flags' => array('jstrue' => 1)), false * @expect false when input array('flags' => array('jstrue' => 1)), false, true * @expect 'Array' when input array('flags' => array('jstrue' => 1, 'jsobj' => 0)), array('a', 'b') * @expect 'a,b' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1, 'mustlam' => 0, 'lambda' => 0)), array('a', 'b') * @expect '[object Object]' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', 'c' => 'b') * @expect '[object Object]' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('c' => 'b') * @expect 'a,true' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1, 'mustlam' => 0, 'lambda' => 0)), array('a', true) * @expect 'a,1' when input array('flags' => array('jstrue' => 0, 'jsobj' => 1, 'mustlam' => 0, 'lambda' => 0)), array('a',true) * @expect 'a,' when input array('flags' => array('jstrue' => 0, 'jsobj' => 1, 'mustlam' => 0, 'lambda' => 0)), array('a',false) * @expect 'a,false' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1, 'mustlam' => 0, 'lambda' => 0)), array('a',false) */ public static function raw($cx, $v, $ex = 0) { if ($ex) { return $v; } if ($v === true) { if ($cx['flags']['jstrue']) { return 'true'; } } if (($v === false)) { if ($cx['flags']['jstrue']) { return 'false'; } } if (is_array($v)) { if ($cx['flags']['jsobj']) { if (count(array_diff_key($v, array_keys(array_keys($v)))) > 0) { return '[object Object]'; } else { $ret = array(); foreach ($v as $k => $vv) { $ret[] = static::raw($cx, $vv); } return join(',', $ret); } } else { return 'Array'; } } return "$v"; } /** * Get html encoded string * * @param array<string,array|string|integer> $cx render time context * @param array<array|string|integer>|string|integer|null $var value to be htmlencoded * * @return string The htmlencoded value of the specified variable * * @expect 'a' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a' * @expect 'a&b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a&b' * @expect 'a'b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a\'b' */ public static function enc($cx, $var) { return htmlspecialchars(static::raw($cx, $var), ENT_QUOTES, 'UTF-8'); } /** * LightnCandy runtime method for {{var}} , and deal with single quote to same as handlebars.js . * * @param array<string,array|string|integer> $cx render time context * @param array<array|string|integer>|string|integer|null $var value to be htmlencoded * * @return string The htmlencoded value of the specified variable * * @expect 'a' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a' * @expect 'a&b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a&b' * @expect 'a'b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a\'b' * @expect '`a'b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), '`a\'b' */ public static function encq($cx, $var) { return str_replace(array('=', '`', '''), array('=', '`', '''), htmlspecialchars(static::raw($cx, $var), ENT_QUOTES, 'UTF-8')); } } PK ! sx�pR� R� lightncandy/src/Compiler.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file of LightnCandy Compiler * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy Compiler */ class Compiler extends Validator { public static $lastParsed; /** * Compile template into PHP code * * @param array<string,array|string|integer> $context Current context * @param string $template handlebars template * * @return string|null generated PHP code */ public static function compileTemplate(&$context, $template) { array_unshift($context['parsed'], array()); Validator::verify($context, $template); static::$lastParsed = $context['parsed']; if (count($context['error'])) { return; } Parser::setDelimiter($context); $context['compile'] = true; // Handle dynamic partials Partial::handleDynamic($context); // Do PHP code generation. $code = ''; foreach ($context['parsed'][0] as $info) { if (is_array($info)) { $context['tokens']['current']++; $code .= "'" . static::compileToken($context, $info) . "'"; } else { $code .= $info; } } array_shift($context['parsed']); return $code; } /** * Compose LightnCandy render codes for include() * * @param array<string,array|string|integer> $context Current context * @param string $code generated PHP code * * @return string Composed PHP code */ public static function composePHPRender($context, $code) { $flagJStrue = Expression::boolString($context['flags']['jstrue']); $flagJSObj = Expression::boolString($context['flags']['jsobj']); $flagJSLen = Expression::boolString($context['flags']['jslen']); $flagSPVar = Expression::boolString($context['flags']['spvar']); $flagProp = Expression::boolString($context['flags']['prop']); $flagMethod = Expression::boolString($context['flags']['method']); $flagLambda = Expression::boolString($context['flags']['lambda']); $flagMustlok = Expression::boolString($context['flags']['mustlok']); $flagMustlam = Expression::boolString($context['flags']['mustlam']); $flagMustsec = Expression::boolString($context['flags']['mustsec']); $flagEcho = Expression::boolString($context['flags']['echo']); $flagPartNC = Expression::boolString($context['flags']['partnc']); $flagKnownHlp = Expression::boolString($context['flags']['knohlp']); $constants = Exporter::constants($context); $helpers = Exporter::helpers($context); $partials = implode(",\n", $context['partialCode']); $debug = Runtime::DEBUG_ERROR_LOG; $use = $context['flags']['standalone'] ? Exporter::runtime($context) : "use {$context['runtime']} as {$context['runtimealias']};"; $stringObject = $context['flags']['method'] || $context['flags']['prop'] ? Exporter::stringobject($context) : ''; $safeString = (($context['usedFeature']['enc'] > 0) && ($context['flags']['standalone'] === 0)) ? "use {$context['safestring']} as SafeString;" : ''; $exportSafeString = (($context['usedFeature']['enc'] > 0) && ($context['flags']['standalone'] >0)) ? Exporter::safestring($context) : ''; // Return generated PHP code string. return <<<VAREND $stringObject{$safeString}{$use}{$exportSafeString}return function (\$in = null, \$options = null) { \$helpers = $helpers; \$partials = array($partials); \$cx = array( 'flags' => array( 'jstrue' => $flagJStrue, 'jsobj' => $flagJSObj, 'jslen' => $flagJSLen, 'spvar' => $flagSPVar, 'prop' => $flagProp, 'method' => $flagMethod, 'lambda' => $flagLambda, 'mustlok' => $flagMustlok, 'mustlam' => $flagMustlam, 'mustsec' => $flagMustsec, 'echo' => $flagEcho, 'partnc' => $flagPartNC, 'knohlp' => $flagKnownHlp, 'debug' => isset(\$options['debug']) ? \$options['debug'] : $debug, ), 'constants' => $constants, 'helpers' => isset(\$options['helpers']) ? array_merge(\$helpers, \$options['helpers']) : \$helpers, 'partials' => isset(\$options['partials']) ? array_merge(\$partials, \$options['partials']) : \$partials, 'scopes' => array(), 'sp_vars' => isset(\$options['data']) ? array_merge(array('root' => \$in), \$options['data']) : array('root' => \$in), 'blparam' => array(), 'partialid' => 0, 'runtime' => '{$context['runtime']}', ); {$context['renderex']} {$context['ops']['array_check']} {$context['ops']['op_start']}'$code'{$context['ops']['op_end']} }; VAREND ; } /** * Get function name for standalone or none standalone template. * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param string $name base function name * @param string $tag original handlabars tag for debug * * @return string compiled Function name * * @expect 'LR::test(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 'LR'), 'test', '' * @expect 'LL::test2(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 'LL'), 'test2', '' * @expect "lala_abctest3(" when input array('flags' => array('standalone' => 1, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 0, 'funcprefix' => 'lala_abc'), 'test3', '' * @expect 'RR::debug(\'abc\', \'test\', ' when input array('flags' => array('standalone' => 0, 'debug' => 1), 'runtime' => 'Runtime', 'runtimealias' => 'RR', 'funcprefix' => 'haha456'), 'test', 'abc' */ protected static function getFuncName(&$context, $name, $tag) { static::addUsageCount($context, 'runtime', $name); if ($context['flags']['debug'] && ($name != 'miss')) { $dbg = "'$tag', '$name', "; $name = 'debug'; static::addUsageCount($context, 'runtime', 'debug'); } else { $dbg = ''; } return $context['flags']['standalone'] ? "{$context['funcprefix']}$name($dbg" : "{$context['runtimealias']}::$name($dbg"; } /** * Get string presentation of variables * * @param array<string,array|string|integer> $context current compile context * @param array<array> $vn variable name array. * @param array<string>|null $blockParams block param list * * @return array<string|array> variable names * * @expect array('array(array($in),array())', array('this')) when input array('flags'=>array('spvar'=>true)), array(null) * @expect array('array(array($in,$in),array())', array('this', 'this')) when input array('flags'=>array('spvar'=>true)), array(null, null) * @expect array('array(array(),array(\'a\'=>$in))', array('this')) when input array('flags'=>array('spvar'=>true)), array('a' => null) */ protected static function getVariableNames(&$context, $vn, $blockParams = null) { $vars = array(array(), array()); $exps = array(); foreach ($vn as $i => $v) { $V = static::getVariableNameOrSubExpression($context, $v); if (is_string($i)) { $vars[1][] = "'$i'=>{$V[0]}"; } else { $vars[0][] = $V[0]; } $exps[] = $V[1]; } $bp = $blockParams ? (',array(' . Expression::listString($blockParams) . ')') : ''; return array('array(array(' . implode(',', $vars[0]) . '),array(' . implode(',', $vars[1]) . ")$bp)", $exps); } /** * Get string presentation of a sub expression * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return array<string> code representing passed expression */ public static function compileSubExpression(&$context, $vars) { $ret = static::customHelper($context, $vars, true, true, true); if (($ret === null) && $context['flags']['lambda']) { $ret = static::compileVariable($context, $vars, true, true); } return array($ret ? $ret : '', 'FIXME: $subExpression'); } /** * Get string presentation of a subexpression or a variable * * @param array<array|string|integer> $context current compile context * @param array<array|string|integer> $var variable parsed path * * @return array<string> variable names */ protected static function getVariableNameOrSubExpression(&$context, $var) { return Parser::isSubExp($var) ? static::compileSubExpression($context, $var[1]) : static::getVariableName($context, $var); } /** * Get string presentation of a variable * * @param array<array|string|integer> $var variable parsed path * @param array<array|string|integer> $context current compile context * @param array<string>|null $lookup extra lookup string as valid PHP variable name * * @return array<string> variable names * * @expect array('$in', 'this') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(null) * @expect array('(($inary && isset($in[\'true\'])) ? $in[\'true\'] : null)', '[true]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('true') * @expect array('(($inary && isset($in[\'false\'])) ? $in[\'false\'] : null)', '[false]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('false') * @expect array('true', 'true') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'true') * @expect array('false', 'false') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'false') * @expect array('(($inary && isset($in[\'2\'])) ? $in[\'2\'] : null)', '[2]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('2') * @expect array('2', '2') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0)), array(-1, '2') * @expect array('(($inary && isset($in[\'@index\'])) ? $in[\'@index\'] : null)', '[@index]') when input array('flags'=>array('spvar'=>false,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@index') * @expect array("(isset(\$cx['sp_vars']['index']) ? \$cx['sp_vars']['index'] : null)", '@[index]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@index') * @expect array("(isset(\$cx['sp_vars']['key']) ? \$cx['sp_vars']['key'] : null)", '@[key]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@key') * @expect array("(isset(\$cx['sp_vars']['first']) ? \$cx['sp_vars']['first'] : null)", '@[first]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@first') * @expect array("(isset(\$cx['sp_vars']['last']) ? \$cx['sp_vars']['last'] : null)", '@[last]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@last') * @expect array('(($inary && isset($in[\'"a"\'])) ? $in[\'"a"\'] : null)', '["a"]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('"a"') * @expect array('"a"', '"a"') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, '"a"') * @expect array('(($inary && isset($in[\'a\'])) ? $in[\'a\'] : null)', '[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('a') * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-1]) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-1]) && isset($cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'] : null)', '../[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(1,'a') * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-3]) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-3]) && isset($cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'] : null)', '../../../[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(3,'a') * @expect array('(($inary && isset($in[\'id\'])) ? $in[\'id\'] : null)', 'this.[id]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(null, 'id') * @expect array('LR::v($cx, $in, isset($in) ? $in : null, array(\'id\'))', 'this.[id]') when input array('flags'=>array('prop'=>true,'spvar'=>true,'debug'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0,'standalone'=>0), 'runtime' => 'Runtime', 'runtimealias' => 'LR'), array(null, 'id') */ protected static function getVariableName(&$context, $var, $lookup = null, $args = null) { if (isset($var[0]) && ($var[0] === Parser::LITERAL)) { if ($var[1] === "undefined") { $var[1] = "null"; } return array($var[1], preg_replace('/\'(.*)\'/', '$1', $var[1])); } list($levels, $spvar, $var) = Expression::analyze($context, $var); $exp = Expression::toString($levels, $spvar, $var); $base = $spvar ? "\$cx['sp_vars']" : '$in'; // change base when trace to parent if ($levels > 0) { if ($spvar) { $base .= str_repeat("['_parent']", $levels); } else { $base = "\$cx['scopes'][count(\$cx['scopes'])-$levels]"; } } if ((empty($var) || (count($var) == 0) || (($var[0] === null) && (count($var) == 1))) && ($lookup === null)) { return array($base, $exp); } if ((count($var) > 0) && ($var[0] === null)) { array_shift($var); } // To support recursive context lookup, instance properties + methods and lambdas // the only way is using slower rendering time variable resolver. if ($context['flags']['prop'] || $context['flags']['method'] || $context['flags']['mustlok'] || $context['flags']['mustlam'] || $context['flags']['lambda']) { $L = Expression::listString($var); $L = ($L === '') ? array() : array($L); if ($lookup) { $L[] = $lookup[0]; } $A = $args ? ",$args[0]" : ''; $E = $args ? ' ' . implode(' ', $args[1]) : ''; return array(static::getFuncName($context, 'v', $exp) . "\$cx, \$in, isset($base) ? $base : null, array(" . implode(',', $L) . ")$A)", $lookup ? "lookup $exp $lookup[1]" : "$exp$E"); } $n = Expression::arrayString($var); $k = array_pop($var); $L = $lookup ? "[{$lookup[0]}]" : ''; $p = $lookup ? $n : (count($var) ? Expression::arrayString($var) : ''); $checks = array(); if ($levels > 0) { $checks[] = "isset($base)"; } if (!$spvar) { if (($levels === 0) && $p) { $checks[] = "isset($base$p)"; } $checks[] = ("$base$p" == '$in') ? '$inary' : "is_array($base$p)"; } $checks[] = "isset($base$n$L)"; $check = ((count($checks) > 1) ? '(' : '') . implode(' && ', $checks) . ((count($checks) > 1) ? ')' : ''); $lenStart = ''; $lenEnd = ''; if ($context['flags']['jslen']) { if (($lookup === null) && ($k === 'length')) { array_pop($checks); $lenStart = '(' . ((count($checks) > 1) ? '(' : '') . implode(' && ', $checks) . ((count($checks) > 1) ? ')' : '') . " ? count($base" . Expression::arrayString($var) . ') : '; $lenEnd = ')'; } } return array("($check ? $base$n$L : $lenStart" . ($context['flags']['debug'] ? (static::getFuncName($context, 'miss', '') . "\$cx, '$exp')") : 'null') . ")$lenEnd", $lookup ? "lookup $exp $lookup[1]" : $exp); } /** * Return compiled PHP code for a handlebars token * * @param array<string,array|string|integer> $context current compile context * @param array<string,array|boolean> $info parsed information * * @return string Return compiled code segment for the token */ protected static function compileToken(&$context, $info) { list($raw, $vars, $token, $indent) = $info; $context['tokens']['partialind'] = $indent; $context['currentToken'] = $token; if ($ret = static::operator($token[Token::POS_OP], $context, $vars)) { return $ret; } if (isset($vars[0][0])) { if ($ret = static::customHelper($context, $vars, $raw, true)) { return static::compileOutput($context, $ret, 'FIXME: helper', $raw, false); } if ($context['flags']['else'] && ($vars[0][0] === 'else')) { return static::doElse($context, $vars); } if ($vars[0][0] === 'lookup') { return static::compileLookup($context, $vars, $raw); } if ($vars[0][0] === 'log') { return static::compileLog($context, $vars, $raw); } } return static::compileVariable($context, $vars, $raw, false); } /** * handle partial * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return string Return compiled code segment for the partial */ public static function partial(&$context, $vars) { Parser::getBlockParams($vars); $pid = Parser::getPartialBlock($vars); $p = array_shift($vars); if ($context['flags']['runpart']) { if (!isset($vars[0])) { $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array(); } $v = static::getVariableNames($context, $vars); $tag = ">$p[0] " .implode(' ', $v[1]); if (Parser::isSubExp($p)) { list($p) = static::compileSubExpression($context, $p[1]); } else { $p = "'$p[0]'"; } $sp = $context['tokens']['partialind'] ? ", '{$context['tokens']['partialind']}'" : ''; return $context['ops']['seperator'] . static::getFuncName($context, 'p', $tag) . "\$cx, $p, $v[0],$pid$sp){$context['ops']['seperator']}"; } return isset($context['usedPartial'][$p[0]]) ? "{$context['ops']['seperator']}'" . Partial::compileStatic($context, $p[0]) . "'{$context['ops']['seperator']}" : $context['ops']['seperator']; } /** * handle inline partial * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return string Return compiled code segment for the partial */ public static function inline(&$context, $vars) { Parser::getBlockParams($vars); list($code) = array_shift($vars); $p = array_shift($vars); if (!isset($vars[0])) { $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array(); } $v = static::getVariableNames($context, $vars); $tag = ">*inline $p[0]" .implode(' ', $v[1]); return $context['ops']['seperator'] . static::getFuncName($context, 'in', $tag) . "\$cx, '{$p[0]}', $code){$context['ops']['seperator']}"; } /** * Return compiled PHP code for a handlebars inverted section begin token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return string Return compiled code segment for the token */ protected static function invertedSection(&$context, $vars) { $v = static::getVariableName($context, $vars[0]); return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'isec', '^' . $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}"; } /** * Return compiled PHP code for a handlebars block custom helper begin token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $inverted the logic will be inverted * * @return string Return compiled code segment for the token */ protected static function blockCustomHelper(&$context, $vars, $inverted = false) { $bp = Parser::getBlockParams($vars); $ch = array_shift($vars); $inverted = $inverted ? 'true' : 'false'; static::addUsageCount($context, 'helpers', $ch[0]); $v = static::getVariableNames($context, $vars, $bp); return $context['ops']['seperator'] . static::getFuncName($context, 'hbbch', ($inverted ? '^' : '#') . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, \$in, $inverted, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}"; } /** * Return compiled PHP code for a handlebars block end token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param string|null $matchop should also match to this operator * * @return string Return compiled code segment for the token */ protected static function blockEnd(&$context, &$vars, $matchop = null) { $pop = $context['stack'][count($context['stack']) - 1]; switch (isset($context['helpers'][$context['currentToken'][Token::POS_INNERTAG]]) ? 'skip' : $context['currentToken'][Token::POS_INNERTAG]) { case 'if': case 'unless': if ($pop === ':') { array_pop($context['stack']); return "{$context['ops']['cnd_end']}"; } if (!$context['flags']['nohbh']) { return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}"; } break; case 'with': if (!$context['flags']['nohbh']) { return "{$context['ops']['f_end']}}){$context['ops']['seperator']}"; } } if ($pop === ':') { array_pop($context['stack']); return "{$context['ops']['f_end']}}){$context['ops']['seperator']}"; } switch ($pop) { case '#': return "{$context['ops']['f_end']}}){$context['ops']['seperator']}"; case '^': return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}"; } } /** * Return compiled PHP code for a handlebars block begin token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return string Return compiled code segment for the token */ protected static function blockBegin(&$context, $vars) { $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array()); if (!$context['flags']['nohbh']) { switch (isset($vars[0][0]) ? $vars[0][0] : null) { case 'if': $includeZero = (isset($vars['includeZero'][1]) && $vars['includeZero'][1]) ? 'true' : 'false'; return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]}, {$includeZero})){$context['ops']['cnd_then']}"; case 'unless': return "{$context['ops']['cnd_start']}(!" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]}, false)){$context['ops']['cnd_then']}"; case 'each': return static::section($context, $vars, true); case 'with': if ($r = static::with($context, $vars)) { return $r; } } } return static::section($context, $vars); } /** * compile {{#foo}} token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $isEach the section is #each * * @return string|null Return compiled code segment for the token */ protected static function section(&$context, $vars, $isEach = false) { $bs = 'null'; $be = ''; if ($isEach) { $bp = Parser::getBlockParams($vars); $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null'; $be = $bp ? (' as |' . implode(' ', $bp) . '|') : ''; array_shift($vars); } if ($context['flags']['lambda'] && !$isEach) { $V = array_shift($vars); $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array(''))); } else { $v = static::getVariableNameOrSubExpression($context, $vars[0]); } $each = $isEach ? 'true' : 'false'; return $context['ops']['seperator'] . static::getFuncName($context, 'sec', ($isEach ? 'each ' : '') . $v[1] . $be) . "\$cx, {$v[0]}, $bs, \$in, $each, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}"; } /** * compile {{with}} token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return string|null Return compiled code segment for the token */ protected static function with(&$context, $vars) { $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array()); $bp = Parser::getBlockParams($vars); $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null'; $be = $bp ? " as |$bp[0]|" : ''; return $context['ops']['seperator'] . static::getFuncName($context, 'wi', 'with ' . $v[1] . $be) . "\$cx, {$v[0]}, $bs, \$in, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}"; } /** * Return compiled PHP code for a handlebars custom helper token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $raw is this {{{ token or not * @param boolean $nosep true to compile without seperator * @param boolean $subExp true when compile for subexpression * * @return string|null Return compiled code segment for the token when the token is custom helper */ protected static function customHelper(&$context, $vars, $raw, $nosep, $subExp = false) { if (count($vars[0]) > 1) { return; } if (!isset($context['helpers'][$vars[0][0]])) { if ($subExp) { if ($vars[0][0] == 'lookup') { return static::compileLookup($context, $vars, $raw, true); } } return; } $fn = $raw ? 'raw' : $context['ops']['enc']; $ch = array_shift($vars); $v = static::getVariableNames($context, $vars); static::addUsageCount($context, 'helpers', $ch[0]); $sep = $nosep ? '' : $context['ops']['seperator']; return $sep . static::getFuncName($context, 'hbch', "$ch[0] " . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, '$fn', \$in)$sep"; } /** * Return compiled PHP code for a handlebars else token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return string Return compiled code segment for the token when the token is else */ protected static function doElse(&$context, $vars) { $v = $context['stack'][count($context['stack']) - 2]; if ((($v === '[if]') && !isset($context['helpers']['if'])) || (($v === '[unless]') && !isset($context['helpers']['unless']))) { $context['stack'][] = ':'; return "{$context['ops']['cnd_else']}"; } return "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['array_check']}{$context['ops']['f_start']}"; } /** * Return compiled PHP code for a handlebars log token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $raw is this {{{ token or not * * @return string Return compiled code segment for the token */ protected static function compileLog(&$context, &$vars, $raw) { array_shift($vars); $v = static::getVariableNames($context, $vars); return $context['ops']['seperator'] . static::getFuncName($context, 'lo', $v[1]) . "\$cx, {$v[0]}){$context['ops']['seperator']}"; } /** * Return compiled PHP code for a handlebars lookup token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $raw is this {{{ token or not * @param boolean $nosep true to compile without seperator * * @return string Return compiled code segment for the token */ protected static function compileLookup(&$context, &$vars, $raw, $nosep = false) { $v2 = static::getVariableName($context, $vars[2]); $v = static::getVariableName($context, $vars[1], $v2); $sep = $nosep ? '' : $context['ops']['seperator']; $ex = $nosep ? ', 1' : ''; if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug']) { return $sep . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $v[1]) . "\$cx, {$v[0]}$ex){$sep}"; } else { return $raw ? "{$sep}$v[0]{$sep}" : "{$sep}htmlspecialchars((string){$v[0]}, ENT_QUOTES, 'UTF-8'){$sep}"; } } /** * Return compiled PHP code for template output * * @param array<string,array|string|integer> $context current compile context * @param string $variable PHP code for the variable * @param string $expression normalized handlebars expression * @param boolean $raw is this {{{ token or not * @param boolean $nosep true to compile without seperator * * @return string Return compiled code segment for the token */ protected static function compileOutput(&$context, $variable, $expression, $raw, $nosep) { $sep = $nosep ? '' : $context['ops']['seperator']; if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug'] || $nosep) { return $sep . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $expression) . "\$cx, $variable)$sep"; } else { return $raw ? "$sep$variable{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlspecialchars((string)$variable, ENT_QUOTES, 'UTF-8')$sep"; } } /** * Return compiled PHP code for a handlebars variable token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $raw is this {{{ token or not * @param boolean $nosep true to compile without seperator * * @return string Return compiled code segment for the token */ protected static function compileVariable(&$context, &$vars, $raw, $nosep) { if ($context['flags']['lambda']) { $V = array_shift($vars); $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array(''))); } else { $v = static::getVariableName($context, $vars[0]); } return static::compileOutput($context, $v[0], $v[1], $raw, $nosep); } /** * Add usage count to context * * @param array<string,array|string|integer> $context current context * @param string $category category name, can be one of: 'var', 'helpers', 'runtime' * @param string $name used name * @param integer $count increment * * @expect 1 when input array('usedCount' => array('test' => array())), 'test', 'testname' * @expect 3 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname' * @expect 5 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname', 3 */ protected static function addUsageCount(&$context, $category, $name, $count = 1) { if (!isset($context['usedCount'][$category][$name])) { $context['usedCount'][$category][$name] = 0; } return ($context['usedCount'][$category][$name] += $count); } } PK ! +���+ �+ lightncandy/src/Context.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to handle LightnCandy Context * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy class to handle Context */ class Context extends Flags { /** * Create a context from options * * @param array<string,array|string|integer> $options input options * * @return array<string,array|string|integer> Context from options */ public static function create($options) { if (!is_array($options)) { $options = array(); } $flags = isset($options['flags']) ? $options['flags'] : static::FLAG_BESTPERFORMANCE; $context = array( 'flags' => array( 'errorlog' => $flags & static::FLAG_ERROR_LOG, 'exception' => $flags & static::FLAG_ERROR_EXCEPTION, 'skippartial' => $flags & static::FLAG_ERROR_SKIPPARTIAL, 'standalone' => $flags & static::FLAG_STANDALONEPHP, 'noesc' => $flags & static::FLAG_NOESCAPE, 'jstrue' => $flags & static::FLAG_JSTRUE, 'jsobj' => $flags & static::FLAG_JSOBJECT, 'jslen' => $flags & static::FLAG_JSLENGTH, 'hbesc' => $flags & static::FLAG_HBESCAPE, 'this' => $flags & static::FLAG_THIS, 'nohbh' => $flags & static::FLAG_NOHBHELPERS, 'parent' => $flags & static::FLAG_PARENT, 'echo' => $flags & static::FLAG_ECHO, 'advar' => $flags & static::FLAG_ADVARNAME, 'namev' => $flags & static::FLAG_NAMEDARG, 'spvar' => $flags & static::FLAG_SPVARS, 'slash' => $flags & static::FLAG_SLASH, 'else' => $flags & static::FLAG_ELSE, 'exhlp' => $flags & static::FLAG_EXTHELPER, 'lambda' => $flags & static::FLAG_HANDLEBARSLAMBDA, 'mustlok' => $flags & static::FLAG_MUSTACHELOOKUP, 'mustlam' => $flags & static::FLAG_MUSTACHELAMBDA, 'mustsec' => $flags & static::FLAG_MUSTACHESECTION, 'noind' => $flags & static::FLAG_PREVENTINDENT, 'debug' => $flags & static::FLAG_RENDER_DEBUG, 'prop' => $flags & static::FLAG_PROPERTY, 'method' => $flags & static::FLAG_METHOD, 'runpart' => $flags & static::FLAG_RUNTIMEPARTIAL, 'rawblock' => $flags & static::FLAG_RAWBLOCK, 'partnc' => $flags & static::FLAG_PARTIALNEWCONTEXT, 'nostd' => $flags & static::FLAG_IGNORESTANDALONE, 'strpar' => $flags & static::FLAG_STRINGPARAMS, 'knohlp' => $flags & static::FLAG_KNOWNHELPERSONLY, ), 'delimiters' => array( isset($options['delimiters'][0]) ? $options['delimiters'][0] : '{{', isset($options['delimiters'][1]) ? $options['delimiters'][1] : '}}', ), 'level' => 0, 'stack' => array(), 'currentToken' => null, 'error' => array(), 'elselvl' => array(), 'elsechain' => false, 'tokens' => array( 'standalone' => true, 'ahead' => false, 'current' => 0, 'count' => 0, 'partialind' => '', ), 'usedPartial' => array(), 'partialStack' => array(), 'partialCode' => array(), 'usedFeature' => array( 'rootthis' => 0, 'enc' => 0, 'raw' => 0, 'sec' => 0, 'isec' => 0, 'if' => 0, 'else' => 0, 'unless' => 0, 'each' => 0, 'this' => 0, 'parent' => 0, 'with' => 0, 'comment' => 0, 'partial' => 0, 'dynpartial' => 0, 'inlpartial' => 0, 'helper' => 0, 'delimiter' => 0, 'subexp' => 0, 'rawblock' => 0, 'pblock' => 0, 'lookup' => 0, 'log' => 0, ), 'usedCount' => array( 'var' => array(), 'helpers' => array(), 'runtime' => array(), ), 'compile' => false, 'parsed' => array(), 'partials' => (isset($options['partials']) && is_array($options['partials'])) ? $options['partials'] : array(), 'partialblock' => array(), 'inlinepartial' => array(), 'helpers' => array(), 'renderex' => isset($options['renderex']) ? $options['renderex'] : '', 'prepartial' => (isset($options['prepartial']) && is_callable($options['prepartial'])) ? $options['prepartial'] : false, 'helperresolver' => (isset($options['helperresolver']) && is_callable($options['helperresolver'])) ? $options['helperresolver'] : false, 'partialresolver' => (isset($options['partialresolver']) && is_callable($options['partialresolver'])) ? $options['partialresolver'] : false, 'runtime' => isset($options['runtime']) ? $options['runtime'] : '\\LightnCandy\\Runtime', 'runtimealias' => 'LR', 'safestring' => '\\LightnCandy\\SafeString', 'safestringalias' => isset($options['safestring']) ? $options['safestring'] : 'LS', 'rawblock' => false, 'funcprefix' => uniqid('lcr'), ); $context['ops'] = $context['flags']['echo'] ? array( 'seperator' => ',', 'f_start' => 'echo ', 'f_end' => ';', 'op_start' => 'ob_start();echo ', 'op_end' => ';return ob_get_clean();', 'cnd_start' => ';if ', 'cnd_then' => '{echo ', 'cnd_else' => ';}else{echo ', 'cnd_end' => ';}echo ', 'cnd_nend' => ';}', ) : array( 'seperator' => '.', 'f_start' => 'return ', 'f_end' => ';', 'op_start' => 'return ', 'op_end' => ';', 'cnd_start' => '.(', 'cnd_then' => ' ? ', 'cnd_else' => ' : ', 'cnd_end' => ').', 'cnd_nend' => ')', ); $context['ops']['enc'] = $context['flags']['hbesc'] ? 'encq' : 'enc'; $context['ops']['array_check'] = '$inary=is_array($in);'; static::updateHelperTable($context, $options); if ($context['flags']['partnc'] && ($context['flags']['runpart'] == 0)) { $context['error'][] = 'The FLAG_PARTIALNEWCONTEXT requires FLAG_RUNTIMEPARTIAL! Fix your compile options please'; } return $context; } /** * update specific custom helper table from options * * @param array<string,array|string|integer> $context prepared context * @param array<string,array|string|integer> $options input options * @param string $tname helper table name * * @return array<string,array|string|integer> context with generated helper table * * @expect array() when input array(), array() * @expect array('flags' => array('exhlp' => 1), 'helpers' => array('abc' => 1)) when input array('flags' => array('exhlp' => 1)), array('helpers' => array('abc')) * @expect array('error' => array('You provide a custom helper named as \'abc\' in options[\'helpers\'], but the function abc() is not defined!'), 'flags' => array('exhlp' => 0)) when input array('error' => array(), 'flags' => array('exhlp' => 0)), array('helpers' => array('abc')) * @expect array('flags' => array('exhlp' => 1), 'helpers' => array('\\LightnCandy\\Runtime::raw' => '\\LightnCandy\\Runtime::raw')) when input array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('\\LightnCandy\\Runtime::raw')) * @expect array('flags' => array('exhlp' => 1), 'helpers' => array('test' => '\\LightnCandy\\Runtime::raw')) when input array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('test' => '\\LightnCandy\\Runtime::raw')) */ protected static function updateHelperTable(&$context, $options, $tname = 'helpers') { if (isset($options[$tname]) && is_array($options[$tname])) { foreach ($options[$tname] as $name => $func) { $tn = is_int($name) ? $func : $name; if (is_callable($func)) { $context[$tname][$tn] = $func; } else { if (is_array($func)) { $context['error'][] = "I found an array in $tname with key as $name, please fix it."; } else { if ($context['flags']['exhlp']) { // Regist helper names only $context[$tname][$tn] = 1; } else { $context['error'][] = "You provide a custom helper named as '$tn' in options['$tname'], but the function $func() is not defined!"; } } } } } return $context; } /** * Merge a context into another * * @param array<string,array|string|integer> $context master context * @param array<string,array|string|integer> $tmp another context will be overwrited into master context */ public static function merge(&$context, $tmp) { $context['error'] = $tmp['error']; $context['helpers'] = $tmp['helpers']; $context['partials'] = $tmp['partials']; $context['partialCode'] = $tmp['partialCode']; $context['partialStack'] = $tmp['partialStack']; $context['usedCount'] = $tmp['usedCount']; $context['usedFeature'] = $tmp['usedFeature']; $context['usedPartial'] = $tmp['usedPartial']; } } PK ! �'Ɉ� � lightncandy/src/SafeString.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to keep LightnCandy string utilities * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy SafeString class */ class SafeString extends Encoder { const EXTENDED_COMMENT_SEARCH = '/{{!--.*?--}}/s'; const IS_SUBEXP_SEARCH = '/^\(.+\)$/s'; const IS_BLOCKPARAM_SEARCH = '/^ +\|(.+)\|$/s'; private $string; public static $jsContext = array( 'flags' => array( 'jstrue' => 1, 'jsobj' => 1, ) ); /** * Constructor * * @param string $str input string * @param bool|string $escape false to not escape, true to escape, 'encq' to escape as handlebars.js */ public function __construct($str, $escape = false) { $this->string = $escape ? (($escape === 'encq') ? static::encq(static::$jsContext, $str) : static::enc(static::$jsContext, $str)) : $str; } public function __toString() { return $this->string; } /** * Strip extended comments {{!-- .... --}} * * @param string $template handlebars template string * * @return string Stripped template * * @expect 'abc' when input 'abc' * @expect 'abc{{!}}cde' when input 'abc{{!}}cde' * @expect 'abc{{! }}cde' when input 'abc{{!----}}cde' */ public static function stripExtendedComments($template) { return preg_replace(static::EXTENDED_COMMENT_SEARCH, '{{! }}', $template); } /** * Escape template * * @param string $template handlebars template string * * @return string Escaped template * * @expect 'abc' when input 'abc' * @expect 'a\\\\bc' when input 'a\bc' * @expect 'a\\\'bc' when input 'a\'bc' */ public static function escapeTemplate($template) { return addcslashes(addcslashes($template, '\\'), "'"); } } PK ! �˶(l l lightncandy/src/Expression.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file of LightnCandy Expression handler * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy Expression handler */ class Expression { /** * return 'true' or 'false' string. * * @param integer $v value * * @return string 'true' when the value larger then 0 * * @expect 'true' when input 1 * @expect 'true' when input 999 * @expect 'false' when input 0 * @expect 'false' when input -1 */ public static function boolString($v) { return ($v > 0) ? 'true' : 'false'; } /** * Get string presentation for a string list * * @param array<string> $list an array of strings. * * @return string PHP list string * * @expect '' when input array() * @expect "'a'" when input array('a') * @expect "'a','b','c'" when input array('a', 'b', 'c') */ public static function listString($list) { return implode(',', (array_map(function ($v) { return "'$v'"; }, $list))); } /** * Get string presentation for an array * * @param array<string> $list an array of variable names. * * @return string PHP array names string * * @expect '' when input array() * @expect "['a']" when input array('a') * @expect "['a']['b']['c']" when input array('a', 'b', 'c') */ public static function arrayString($list) { return implode('', (array_map(function ($v) { return "['$v']"; }, $list))); } /** * Analyze an expression * * @param array<string,array|string|integer> $context Current context * @param array<array|string|integer> $var variable parsed path * * @return array<integer|boolean|array> analyzed result * * @expect array(0, false, array('foo')) when input array('flags' => array('spvar' => 0)), array(0, 'foo') * @expect array(1, false, array('foo')) when input array('flags' => array('spvar' => 0)), array(1, 'foo') */ public static function analyze($context, $var) { $levels = 0; $spvar = false; if (isset($var[0])) { // trace to parent if (!is_string($var[0]) && is_int($var[0])) { $levels = array_shift($var); } } if (isset($var[0])) { // handle @root, @index, @key, @last, etc if ($context['flags']['spvar']) { if (substr($var[0], 0, 1) === '@') { $spvar = true; $var[0] = substr($var[0], 1); } } } return array($levels, $spvar, $var); } /** * get normalized handlebars expression for a variable * * @param integer $levels trace N levels top parent scope * @param boolean $spvar is the path start with @ or not * @param array<string|integer> $var variable parsed path * * @return string normalized expression for debug display * * @expect '[a].[b]' when input 0, false, array('a', 'b') * @expect '@[root]' when input 0, true, array('root') * @expect 'this' when input 0, false, null * @expect 'this.[id]' when input 0, false, array(null, 'id') * @expect '@[root].[a].[b]' when input 0, true, array('root', 'a', 'b') * @expect '../../[a].[b]' when input 2, false, array('a', 'b') * @expect '../[a\'b]' when input 1, false, array('a\'b') */ public static function toString($levels, $spvar, $var) { return ($spvar ? '@' : '') . str_repeat('../', $levels) . ((is_array($var) && count($var)) ? implode('.', array_map(function ($v) { return ($v === null) ? 'this' : "[$v]"; }, $var)) : 'this'); } } PK ! Dm�� � lightncandy/src/Flags.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to keep LightnCandy flags * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy class to keep flag consts */ class Flags { // Compile time error handling flags const FLAG_ERROR_LOG = 1; const FLAG_ERROR_EXCEPTION = 2; // JavaScript compatibility const FLAG_JSTRUE = 8; const FLAG_JSOBJECT = 16; const FLAG_JSLENGTH = 33554432; // Handlebars.js compatibility const FLAG_THIS = 32; const FLAG_PARENT = 128; const FLAG_HBESCAPE = 256; const FLAG_ADVARNAME = 512; const FLAG_NAMEDARG = 2048; const FLAG_SPVARS = 4096; const FLAG_PREVENTINDENT = 131072; const FLAG_SLASH = 8388608; const FLAG_ELSE = 16777216; const FLAG_RAWBLOCK = 134217728; const FLAG_HANDLEBARSLAMBDA = 268435456; const FLAG_PARTIALNEWCONTEXT = 536870912; const FLAG_IGNORESTANDALONE = 1073741824; const FLAG_STRINGPARAMS = 2147483648; const FLAG_KNOWNHELPERSONLY = 4294967296; // PHP behavior flags const FLAG_STANDALONEPHP = 4; const FLAG_EXTHELPER = 8192; const FLAG_ECHO = 16384; const FLAG_PROPERTY = 32768; const FLAG_METHOD = 65536; const FLAG_RUNTIMEPARTIAL = 1048576; const FLAG_NOESCAPE = 67108864; // Mustache compatibility const FLAG_MUSTACHELOOKUP = 262144; const FLAG_ERROR_SKIPPARTIAL = 4194304; const FLAG_MUSTACHELAMBDA = 2097152; const FLAG_NOHBHELPERS = 64; const FLAG_MUSTACHESECTION = 8589934592; // Template rendering time debug flags const FLAG_RENDER_DEBUG = 524288; // alias flags const FLAG_BESTPERFORMANCE = 16388; // FLAG_ECHO + FLAG_STANDALONEPHP const FLAG_JS = 33554456; // FLAG_JSTRUE + FLAG_JSOBJECT + FLAG_JSLENGTH const FLAG_INSTANCE = 98304; // FLAG_PROPERTY + FLAG_METHOD const FLAG_MUSTACHE = 8597536856; // FLAG_ERROR_SKIPPARTIAL + FLAG_MUSTACHELOOKUP + FLAG_MUSTACHELAMBDA + FLAG_NOHBHELPERS + FLAG_MUSTACHESECTION + FLAG_RUNTIMEPARTIAL + FLAG_JSTRUE + FLAG_JSOBJECT const FLAG_HANDLEBARS = 159390624; // FLAG_THIS + FLAG_PARENT + FLAG_HBESCAPE + FLAG_ADVARNAME + FLAG_SPACECTL + FLAG_NAMEDARG + FLAG_SPVARS + FLAG_SLASH + FLAG_ELSE + FLAG_RAWBLOCK const FLAG_HANDLEBARSJS = 192945080; // FLAG_JS + FLAG_HANDLEBARS const FLAG_HANDLEBARSJS_FULL = 429235128; // FLAG_HANDLEBARSJS + FLAG_INSTANCE + FLAG_RUNTIMEPARTIAL + FLAG_MUSTACHELOOKUP } PK ! �2/e e lightncandy/src/LightnCandy.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * the major file of LightnCandy compiler * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy major static class */ class LightnCandy extends Flags { protected static $lastContext; public static $lastParsed; /** * Compile handlebars template into PHP code. * * @param string $template handlebars template string * @param array<string,array|string|integer> $options LightnCandy compile time and run time options, default is array('flags' => LightnCandy::FLAG_BESTPERFORMANCE) * * @return string|false Compiled PHP code when successed. If error happened and compile failed, return false. */ public static function compile($template, $options = array('flags' => self::FLAG_BESTPERFORMANCE)) { $context = Context::create($options); if (static::handleError($context)) { return false; } $code = Compiler::compileTemplate($context, SafeString::escapeTemplate($template)); static::$lastParsed = Compiler::$lastParsed; // return false when fatal error if (static::handleError($context)) { return false; } // Or, return full PHP render codes as string return Compiler::composePHPRender($context, $code); } /** * Compile handlebars partial into PHP function code. * * @param string $template handlebars template string * @param array<string,array|string|integer> $options LightnCandy compile time and run time options, default is array('flags' => LightnCandy::FLAG_BESTPERFORMANCE) * * @return string|false Compiled PHP code when successed. If error happened and compile failed, return false. * * @expect false when input '{{"}}', array('flags' => LightnCandy::FLAG_HANDLEBARS) */ public static function compilePartial($template, $options = array('flags' => self::FLAG_BESTPERFORMANCE)) { $context = Context::create($options); if (static::handleError($context)) { return false; } $code = Partial::compile($context, SafeString::escapeTemplate($template)); static::$lastParsed = Compiler::$lastParsed; // return false when fatal error if (static::handleError($context)) { return false; } return $code; } /** * Handle exists error and return error status. * * @param array<string,array|string|integer> $context Current context of compiler progress. * * @throws \Exception * @return boolean True when error detected * * @expect false when input array('error' => array()) * @expect true when input array('error' => array('some error'), 'flags' => array('errorlog' => 0, 'exception' => 0)) */ protected static function handleError(&$context) { static::$lastContext = $context; if (count($context['error'])) { if ($context['flags']['errorlog']) { error_log(implode("\n", $context['error'])); } if ($context['flags']['exception']) { throw new \Exception(implode("\n", $context['error'])); } return true; } return false; } /** * Get last compiler context. * * @return array<string,array|string|integer> Context data */ public static function getContext() { return static::$lastContext; } /** * Get a working render function by a string of PHP code. This method may requires php setting allow_url_include=1 and allow_url_fopen=1 , or access right to tmp file system. * * @param string $php PHP code * @param string|null $tmpDir Optional, change temp directory for php include file saved by prepare() when cannot include PHP code with data:// format. * @param boolean $delete Optional, delete temp php file when set to tru. Default is true, set it to false for debug propose * * @return Closure|false result of include() * * @deprecated */ public static function prepare($php, $tmpDir = null, $delete = true) { $php = "<?php $php ?>"; if (!ini_get('allow_url_include') || !ini_get('allow_url_fopen')) { if (!is_string($tmpDir) || !is_dir($tmpDir)) { $tmpDir = sys_get_temp_dir(); } } if (is_dir($tmpDir)) { $fn = tempnam($tmpDir, 'lci_'); if (!$fn) { error_log("Can not generate tmp file under $tmpDir!!\n"); return false; } if (!file_put_contents($fn, $php)) { error_log("Can not include saved temp php code from $fn, you should add $tmpDir into open_basedir!!\n"); return false; } $phpfunc = include($fn); if ($delete) { unlink($fn); } return $phpfunc; } return include('data://text/plain,' . urlencode($php)); } } PK ! T��[�x �x lightncandy/src/Runtime.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to support LightnCandy compiled PHP runtime * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy class for Object property access on a string. */ class StringObject { protected $string; public function __construct($string) { $this->string = $string; } public function __toString() { return strval($this->string); } } /** * LightnCandy class for compiled PHP runtime. */ class Runtime extends Encoder { const DEBUG_ERROR_LOG = 1; const DEBUG_ERROR_EXCEPTION = 2; const DEBUG_TAGS = 4; const DEBUG_TAGS_ANSI = 12; const DEBUG_TAGS_HTML = 20; /** * Output debug info. * * @param string $v expression * @param string $f runtime function name * @param array<string,array|string|integer> $cx render time context for lightncandy * * @expect '{{123}}' when input '123', 'miss', array('flags' => array('debug' => Runtime::DEBUG_TAGS), 'runtime' => 'LightnCandy\\Runtime'), '' * @expect '<!--MISSED((-->{{#123}}<!--))--><!--SKIPPED--><!--MISSED((-->{{/123}}<!--))-->' when input '123', 'wi', array('flags' => array('debug' => Runtime::DEBUG_TAGS_HTML), 'runtime' => 'LightnCandy\\Runtime'), false, null, false, function () {return 'A';} */ public static function debug($v, $f, $cx) { // Build array of reference for call_user_func_array $P = func_get_args(); $params = array(); for ($i=2;$i<count($P);$i++) { $params[] = &$P[$i]; } $r = call_user_func_array((isset($cx['funcs'][$f]) ? $cx['funcs'][$f] : "{$cx['runtime']}::$f"), $params); if ($cx['flags']['debug'] & static::DEBUG_TAGS) { $ansi = $cx['flags']['debug'] & (static::DEBUG_TAGS_ANSI - static::DEBUG_TAGS); $html = $cx['flags']['debug'] & (static::DEBUG_TAGS_HTML - static::DEBUG_TAGS); $cs = ($html ? (($r !== '') ? '<!!--OK((-->' : '<!--MISSED((-->') : '') . ($ansi ? (($r !== '') ? "\033[0;32m" : "\033[0;31m") : ''); $ce = ($html ? '<!--))-->' : '') . ($ansi ? "\033[0m" : ''); switch ($f) { case 'sec': case 'wi': if ($r == '') { if ($ansi) { $r = "\033[0;33mSKIPPED\033[0m"; } if ($html) { $r = '<!--SKIPPED-->'; } } return "$cs{{#{$v}}}$ce{$r}$cs{{/{$v}}}$ce"; default: return "$cs{{{$v}}}$ce"; } } else { return $r; } } /** * Handle error by error_log or throw exception. * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $err error message * * @throws \Exception */ public static function err($cx, $err) { if ($cx['flags']['debug'] & static::DEBUG_ERROR_LOG) { error_log($err); return; } if ($cx['flags']['debug'] & static::DEBUG_ERROR_EXCEPTION) { throw new \Exception($err); } } /** * Handle missing data error. * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $v expression */ public static function miss($cx, $v) { static::err($cx, "Runtime: $v does not exist"); } /** * For {{log}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $v expression */ public static function lo($cx, $v) { error_log(var_export($v[0], true)); return ''; } /** * Resursive lookup variable and helpers. This is slow and will only be used for instance property or method detection or lambdas. * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array|string|boolean|integer|double|null $in current context * @param array<array|string|integer> $base current variable context * @param array<string|integer> $path array of names for path * @param array|null $args extra arguments for lambda * * @return null|string Return the value or null when not found * * @expect null when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), null, 0, array('a', 'b') * @expect 3 when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0), 'mustlok' => 0), null, array('a' => array('b' => 3)), array('a', 'b') * @expect null when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), null, (Object) array('a' => array('b' => 3)), array('a', 'b') * @expect 3 when input array('scopes' => array(), 'flags' => array('prop' => 1, 'method' => 0, 'mustlok' => 0)), null, (Object) array('a' => array('b' => 3)), array('a', 'b') */ public static function v($cx, $in, $base, $path, $args = null) { $count = count($cx['scopes']); $plen = count($path); while ($base) { $v = $base; foreach ($path as $i => $name) { if (is_array($v)) { if (isset($v[$name])) { $v = $v[$name]; continue; } if (($i === $plen - 1) && ($name === 'length')) { return count($v); } } if (is_object($v)) { if ($cx['flags']['prop'] && !($v instanceof \Closure) && isset($v->$name)) { $v = $v->$name; continue; } if ($cx['flags']['method'] && is_callable(array($v, $name))) { try { $v = $v->$name(); continue; } catch (\BadMethodCallException $e) {} } if ($v instanceof \ArrayAccess) { if (isset($v[$name])) { $v = $v[$name]; continue; } } } if ($cx['flags']['mustlok']) { unset($v); break; } return null; } if (isset($v)) { if ($v instanceof \Closure) { if ($cx['flags']['mustlam'] || $cx['flags']['lambda']) { if (!$cx['flags']['knohlp'] && !is_null($args)) { $A = $args ? $args[0] : array(); $A[] = array('hash' => is_array( $args ) ? $args[1] : null, '_this' => $in); } else { $A = array($in); } $v = call_user_func_array($v, $A); } } return $v; } $count--; switch ($count) { case -1: $base = $cx['sp_vars']['root']; break; case -2: return null; default: $base = $cx['scopes'][$count]; } } if ($args) { static::err($cx, 'Can not find helper or lambda: "' . implode('.', $path) . '" !'); } } /** * For {{#if}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array<array|string|integer>|string|integer|null $v value to be tested * @param boolean $zero include zero as true * * @return boolean Return true when the value is not null nor false. * * @expect false when input array(), null, false * @expect false when input array(), 0, false * @expect true when input array(), 0, true * @expect false when input array(), false, false * @expect true when input array(), true, false * @expect true when input array(), 1, false * @expect false when input array(), '', false * @expect false when input array(), array(), false * @expect true when input array(), array(''), false * @expect true when input array(), array(0), false */ public static function ifvar($cx, $v, $zero) { return ($v !== null) && ($v !== false) && ($zero || ($v !== 0) && ($v !== 0.0)) && ($v !== '') && (is_array($v) ? (count($v) > 0) : true); } /** * For {{^var}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array<array|string|integer>|string|integer|null $v value to be tested * * @return boolean Return true when the value is not null nor false. * * @expect true when input array(), null * @expect false when input array(), 0 * @expect true when input array(), false * @expect false when input array(), 'false' * @expect true when input array(), array() * @expect false when input array(), array('1') */ public static function isec($cx, $v) { return ($v === null) || ($v === false) || (is_array($v) && (count($v) === 0)); } /** * For {{var}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array<array|string|integer>|string|integer|null $var value to be htmlencoded * * @return string The htmlencoded value of the specified variable * * @expect 'a' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a' * @expect 'a&b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a&b' * @expect 'a'b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a\'b' * @expect 'a&b' when input null, new \LightnCandy\SafeString('a&b') */ public static function enc($cx, $var) { // Use full namespace classname for more specific code export/match in Exporter.php replaceSafeString. if ($var instanceof \LightnCandy\SafeString) { return (string)$var; } return htmlspecialchars(static::raw($cx, $var), ENT_QUOTES, 'UTF-8'); } /** * For {{var}} , do html encode just like handlebars.js . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array<array|string|integer>|string|integer|null $var value to be htmlencoded * * @return string The htmlencoded value of the specified variable * * @expect 'a' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a' * @expect 'a&b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a&b' * @expect 'a'b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), 'a\'b' * @expect '`a'b' when input array('flags' => array('mustlam' => 0, 'lambda' => 0)), '`a\'b' */ public static function encq($cx, $var) { // Use full namespace classname for more specific code export/match in Exporter.php replaceSafeString. if ($var instanceof \LightnCandy\SafeString) { return (string)$var; } return str_replace(array('=', '`', '''), array('=', '`', '''), htmlspecialchars(static::raw($cx, $var), ENT_QUOTES, 'UTF-8')); } /** * For {{#var}} or {{#each}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array<array|string|integer>|string|integer|null $v value for the section * @param array<string>|null $bp block parameters * @param array<array|string|integer>|string|integer|null $in input data with current scope * @param boolean $each true when rendering #each * @param Closure $cb callback function to render child context * @param Closure|null $else callback function to render child context when {{else}} * * @return string The rendered string of the section * * @expect '' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), false, null, false, false, function () {return 'A';} * @expect '' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), null, null, null, false, function () {return 'A';} * @expect 'A' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), true, null, true, false, function () {return 'A';} * @expect 'A' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), 0, null, 0, false, function () {return 'A';} * @expect '-a=' when input array('scopes' => array(), 'flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), array('a'), null, array('a'), false, function ($c, $i) {return "-$i=";} * @expect '-a=-b=' when input array('scopes' => array(), 'flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), array('a','b'), null, array('a','b'), false, function ($c, $i) {return "-$i=";} * @expect '' when input array('scopes' => array(), 'flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), 'abc', null, 'abc', true, function ($c, $i) {return "-$i=";} * @expect '-b=' when input array('scopes' => array(), 'flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), array('a' => 'b'), null, array('a' => 'b'), true, function ($c, $i) {return "-$i=";} * @expect 'b' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), 'b', null, 'b', false, function ($c, $i) {return print_r($i, true);} * @expect '1' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), 1, null, 1, false, function ($c, $i) {return print_r($i, true);} * @expect '0' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), 0, null, 0, false, function ($c, $i) {return print_r($i, true);} * @expect '{"b":"c"}' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), array('b' => 'c'), null, array('b' => 'c'), false, function ($c, $i) {return json_encode($i);} * @expect 'inv' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), array(), null, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'inv' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), array(), null, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'inv' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), false, null, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'inv' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), false, null, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'inv' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), '', null, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'cb' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), '', null, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'inv' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), 0, null, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'cb' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), 0, null, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'inv' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'lambda' => 0)), new stdClass, null, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect 'cb' when input array('flags' => array('spvar' => 0, 'mustlam' => 0, 'mustsec' => 0, 'lambda' => 0)), new stdClass, null, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} * @expect '268' when input array('scopes' => array(), 'flags' => array('spvar' => 1, 'mustlam' => 0, 'lambda' => 0), 'sp_vars'=>array('root' => 0)), array(1,3,4), null, 0, false, function ($c, $i) {return $i * 2;} * @expect '038' when input array('scopes' => array(), 'flags' => array('spvar' => 1, 'mustlam' => 0, 'lambda' => 0), 'sp_vars'=>array('root' => 0)), array(1,3,'a'=>4), null, 0, true, function ($c, $i) {return $i * $c['sp_vars']['index'];} */ public static function sec($cx, $v, $bp, $in, $each, $cb, $else = null) { $push = ($in !== $v) || $each; $isAry = is_array($v) || ($v instanceof \ArrayObject); $isTrav = $v instanceof \Traversable; $loop = $each; $keys = null; $last = null; $isObj = false; if ($isAry && $else !== null && count($v) === 0) { return $else($cx, $in); } // #var, detect input type is object or not if (!$loop && $isAry) { $keys = array_keys($v); $loop = (count(array_diff_key($v, array_keys($keys))) == 0); $isObj = !$loop; } if (($loop && $isAry) || $isTrav) { if ($each && !$isTrav) { // Detect input type is object or not when never done once if ($keys == null) { $keys = array_keys($v); $isObj = (count(array_diff_key($v, array_keys($keys))) > 0); } } $ret = array(); if ($push) { $cx['scopes'][] = $in; } $i = 0; if ($cx['flags']['spvar']) { $old_spvar = $cx['sp_vars']; $cx['sp_vars'] = array_merge(array('root' => $old_spvar['root']), $old_spvar, array('_parent' => $old_spvar)); if (!$isTrav) { $last = count($keys) - 1; } } $isSparceArray = $isObj && (count(array_filter(array_keys($v), 'is_string')) == 0); foreach ($v as $index => $raw) { if ($cx['flags']['spvar']) { $cx['sp_vars']['first'] = ($i === 0); $cx['sp_vars']['last'] = ($i == $last); $cx['sp_vars']['key'] = $index; $cx['sp_vars']['index'] = $isSparceArray ? $index : $i; $i++; } if (isset($bp[0])) { $raw = static::m($cx, $raw, array($bp[0] => $raw)); } if (isset($bp[1])) { $raw = static::m($cx, $raw, array($bp[1] => $index)); } $ret[] = $cb($cx, $raw); } if ($cx['flags']['spvar']) { if ($isObj) { unset($cx['sp_vars']['key']); } else { unset($cx['sp_vars']['last']); } unset($cx['sp_vars']['index']); unset($cx['sp_vars']['first']); $cx['sp_vars'] = $old_spvar; } if ($push) { array_pop($cx['scopes']); } return join('', $ret); } if ($each) { if ($else !== null) { $ret = $else($cx, $v); return $ret; } return ''; } if ($isAry) { if ($push) { $cx['scopes'][] = $in; } $ret = $cb($cx, $v); if ($push) { array_pop($cx['scopes']); } return $ret; } if ($cx['flags']['mustsec']) { return $v ? $cb($cx, $in) : ''; } if ($v === true) { return $cb($cx, $in); } if (($v !== null) && ($v !== false)) { return $cb($cx, $v); } if ($else !== null) { $ret = $else($cx, $in); return $ret; } return ''; } /** * For {{#with}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array<array|string|integer>|string|integer|null $v value to be the new context * @param array<array|string|integer>|string|integer|null $in input data with current scope * @param array<string>|null $bp block parameters * @param Closure $cb callback function to render child context * @param Closure|null $else callback function to render child context when {{else}} * * @return string The rendered string of the token * * @expect '' when input array(), false, null, false, function () {return 'A';} * @expect '' when input array(), null, null, null, function () {return 'A';} * @expect '{"a":"b"}' when input array(), array('a'=>'b'), null, array('a'=>'c'), function ($c, $i) {return json_encode($i);} * @expect '-b=' when input array(), 'b', null, array('a'=>'b'), function ($c, $i) {return "-$i=";} */ public static function wi($cx, $v, $bp, $in, $cb, $else = null) { if (isset($bp[0])) { $v = static::m($cx, $v, array($bp[0] => $v)); } if (($v === false) || ($v === null) || (is_array($v) && (count($v) === 0))) { return $else ? $else($cx, $in) : ''; } if ($v === $in) { $ret = $cb($cx, $v); } else { $cx['scopes'][] = $in; $ret = $cb($cx, $v); array_pop($cx['scopes']); } return $ret; } /** * Get merged context. * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param array<array|string|integer>|string|integer|null $a the context to be merged * @param array<array|string|integer>|string|integer|null $b the new context to overwrite * * @return array<array|string|integer>|string|integer the merged context object * */ public static function m($cx, $a, $b) { if (is_array($b)) { if ($a === null) { return $b; } elseif (is_array($a)) { return array_merge($a, $b); } elseif ($cx['flags']['method'] || $cx['flags']['prop']) { if (!is_object($a)) { $a = new StringObject($a); } foreach ($b as $i => $v) { $a->$i = $v; } } } return $a; } /** * For {{> partial}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $p partial name * @param array<array|string|integer>|string|integer|null $v value to be the new context * * @return string The rendered string of the partial * */ public static function p($cx, $p, $v, $pid, $sp = '') { $pp = ($p === '@partial-block') ? "$p" . ($pid > 0 ? $pid : $cx['partialid']) : $p; if (!isset($cx['partials'][$pp])) { static::err($cx, "Can not find partial named as '$p' !!"); return ''; } $cx['partialid'] = ($p === '@partial-block') ? (($pid > 0) ? $pid : (($cx['partialid'] > 0) ? $cx['partialid'] - 1 : 0)) : $pid; return call_user_func($cx['partials'][$pp], $cx, static::m($cx, $v[0][0], $v[1]), $sp); } /** * For {{#* inlinepartial}} . * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $p partial name * @param Closure $code the compiled partial code * */ public static function in(&$cx, $p, $code) { $cx['partials'][$p] = $code; } /* For single custom helpers. * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $ch the name of custom helper to be executed * @param array<array|string|integer>|string|integer|null $vars variables for the helper * @param string $op the name of variable resolver. should be one of: 'raw', 'enc', or 'encq'. * @param array<string,array|string|integer> $_this current rendering context for the helper * * @return string The rendered string of the token */ public static function hbch(&$cx, $ch, $vars, $op, &$_this) { if (isset($cx['blparam'][0][$ch])) { return $cx['blparam'][0][$ch]; } $options = array( 'name' => $ch, 'hash' => $vars[1], 'contexts' => count($cx['scopes']) ? $cx['scopes'] : array(null), 'fn.blockParams' => 0, '_this' => &$_this ); if ($cx['flags']['spvar']) { $options['data'] = &$cx['sp_vars']; } return static::exch($cx, $ch, $vars, $options); } /** * For block custom helpers. * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $ch the name of custom helper to be executed * @param array<array|string|integer>|string|integer|null $vars variables for the helper * @param array<string,array|string|integer> $_this current rendering context for the helper * @param boolean $inverted the logic will be inverted * @param Closure|null $cb callback function to render child context * @param Closure|null $else callback function to render child context when {{else}} * * @return string The rendered string of the token */ public static function hbbch(&$cx, $ch, $vars, &$_this, $inverted, $cb, $else = null) { $options = array( 'name' => $ch, 'hash' => $vars[1], 'contexts' => count($cx['scopes']) ? $cx['scopes'] : array(null), 'fn.blockParams' => 0, '_this' => &$_this, ); if ($cx['flags']['spvar']) { $options['data'] = &$cx['sp_vars']; } if (isset($vars[2])) { $options['fn.blockParams'] = count($vars[2]); } // $invert the logic if ($inverted) { $tmp = $else; $else = $cb; $cb = $tmp; } $options['fn'] = function ($context = '_NO_INPUT_HERE_', $data = null) use ($cx, &$_this, $cb, $options, $vars) { if ($cx['flags']['echo']) { ob_start(); } if (isset($data['data'])) { $old_spvar = $cx['sp_vars']; $cx['sp_vars'] = array_merge(array('root' => $old_spvar['root']), $data['data'], array('_parent' => $old_spvar)); } $ex = false; if (isset($data['blockParams']) && isset($vars[2])) { $ex = array_combine($vars[2], array_slice($data['blockParams'], 0, count($vars[2]))); array_unshift($cx['blparam'], $ex); } elseif (isset($cx['blparam'][0])) { $ex = $cx['blparam'][0]; } if (($context === '_NO_INPUT_HERE_') || ($context === $_this)) { $ret = $cb($cx, is_array($ex) ? static::m($cx, $_this, $ex) : $_this); } else { $cx['scopes'][] = $_this; $ret = $cb($cx, is_array($ex) ? static::m($cx, $context, $ex) : $context); array_pop($cx['scopes']); } if (isset($data['data'])) { $cx['sp_vars'] = $old_spvar; } return $cx['flags']['echo'] ? ob_get_clean() : $ret; }; if ($else) { $options['inverse'] = function ($context = '_NO_INPUT_HERE_') use ($cx, $_this, $else) { if ($cx['flags']['echo']) { ob_start(); } if ($context === '_NO_INPUT_HERE_') { $ret = $else($cx, $_this); } else { $cx['scopes'][] = $_this; $ret = $else($cx, $context); array_pop($cx['scopes']); } return $cx['flags']['echo'] ? ob_get_clean() : $ret; }; } else { $options['inverse'] = function () { return ''; }; } return static::exch($cx, $ch, $vars, $options); } /** * Execute custom helper with prepared options * * @param array<string,array|string|integer> $cx render time context for lightncandy * @param string $ch the name of custom helper to be executed * @param array<array|string|integer>|string|integer|null $vars variables for the helper * @param array<string,array|string|integer> $options the options object * * @return string The rendered string of the token */ public static function exch($cx, $ch, $vars, &$options) { $args = $vars[0]; $args[] = &$options; $r = true; try { $r = call_user_func_array($cx['helpers'][$ch], $args); } catch (\Throwable $E) { static::err($cx, "Runtime: call custom helper '$ch' error: " . $E->getMessage()); } return $r; } } PK ! wm} } lightncandy/src/Token.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to handle LightnCandy token * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy Token handler */ class Token { // RegExps const VARNAME_SEARCH = '/(\\[[^\\]]+\\]|[^\\[\\]\\.]+)/'; // Positions of matched token const POS_LOTHER = 1; const POS_LSPACE = 2; const POS_BEGINTAG = 3; const POS_LSPACECTL = 4; const POS_BEGINRAW = 5; const POS_OP = 6; const POS_INNERTAG = 7; const POS_ENDRAW = 8; const POS_RSPACECTL = 9; const POS_ENDTAG = 10; const POS_RSPACE = 11; const POS_ROTHER = 12; const POS_BACKFILL = 13; /** * Setup delimiter by default or provided string * * @param array<string,array|string|integer> $context Current context * @param string|null $left left string of a token * @param string|null $right right string of a token */ public static function setDelimiter(&$context, $left = null, $right = null) { if ($left === null) { $left = $context['delimiters'][0]; } if ($right === null) { $right = $context['delimiters'][1]; } if (preg_match('/=/', "$left$right")) { $context['error'][] = "Can not set delimiter contains '=' , you try to set delimiter as '$left' and '$right'."; return; } $context['tokens']['startchar'] = substr($left, 0, 1); $context['tokens']['left'] = $left; $context['tokens']['right'] = $right; $rawcount = $context['rawblock'] ? '{2}' : ($context['flags']['rawblock'] ? '{0,2}' : '?'); $left = preg_quote($left); $right = preg_quote($right); $context['tokens']['search'] = "/^(.*?)(\\s*)($left)(~?)(\\{{$rawcount})\\s*([\\^#\\/!&>\\*]{0,2})(.*?)\\s*(\\}{$rawcount})(~?)($right)(\\s*)(.*)\$/s"; } /** * return token string * * @param string[] $token detected handlebars {{ }} token * @param string[]|null $merge list of token strings to be merged * * @return string Return whole token * * @expect 'c' when input array(0, 'a', 'b', 'c', 'd', 'e') * @expect 'cd' when input array(0, 'a', 'b', 'c', 'd', 'e', 'f') * @expect 'qd' when input array(0, 'a', 'b', 'c', 'd', 'e', 'f'), array(3 => 'q') */ public static function toString($token, $merge = null) { if (is_array($merge)) { $token = array_replace($token, $merge); } return implode('', array_slice($token, 3, -2)); } } PK ! ��*�(� (� lightncandy/src/Validator.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to keep LightnCandy Validator * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy Validator */ class Validator { /** * Verify template * * @param array<string,array|string|integer> $context Current context * @param string $template handlebars template */ public static function verify(&$context, $template) { $template = SafeString::stripExtendedComments($template); $context['level'] = 0; Parser::setDelimiter($context); while (preg_match($context['tokens']['search'], $template, $matches)) { // Skip a token when it is slash escaped if ($context['flags']['slash'] && ($matches[Token::POS_LSPACE] === '') && preg_match('/^(.*?)(\\\\+)$/s', $matches[Token::POS_LOTHER], $escmatch)) { if (strlen($escmatch[2]) % 4) { static::pushToken($context, substr($matches[Token::POS_LOTHER], 0, -2) . $context['tokens']['startchar']); $matches[Token::POS_BEGINTAG] = substr($matches[Token::POS_BEGINTAG], 1); $template = implode('', array_slice($matches, Token::POS_BEGINTAG)); continue; } else { $matches[Token::POS_LOTHER] = $escmatch[1] . str_repeat('\\', strlen($escmatch[2]) / 2); } } $context['tokens']['count']++; $V = static::token($matches, $context); static::pushLeft($context); if ($V) { if (is_array($V)) { array_push($V, $matches, $context['tokens']['partialind']); } static::pushToken($context, $V); } $template = "{$matches[Token::POS_RSPACE]}{$matches[Token::POS_ROTHER]}"; } static::pushToken($context, $template); if ($context['level'] > 0) { array_pop($context['stack']); array_pop($context['stack']); $token = array_pop($context['stack']); $context['error'][] = 'Unclosed token ' . ($context['rawblock'] ? "{{{{{$token}}}}}" : ($context['partialblock'] ? "{{#>{$token}}}" : "{{#{$token}}}")) . ' !!'; } } /** * push left string of current token and clear it * * @param array<string,array|string|integer> $context Current context */ protected static function pushLeft(&$context) { $L = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE]; static::pushToken($context, $L); $context['currentToken'][Token::POS_LOTHER] = $context['currentToken'][Token::POS_LSPACE] = ''; } /** * push a string into the partial stacks * * @param array<string,array|string|integer> $context Current context * @param string $append a string to be appended int partial stacks */ protected static function pushPartial(&$context, $append) { $appender = function (&$p) use ($append) { $p .= $append; }; array_walk($context['inlinepartial'], $appender); array_walk($context['partialblock'], $appender); } /** * push a token into the stack when it is not empty string * * @param array<string,array|string|integer> $context Current context * @param string|array $token a parsed token or a string */ protected static function pushToken(&$context, $token) { if ($token === '') { return; } if (is_string($token)) { static::pushPartial($context, $token); if (is_string(end($context['parsed'][0]))) { $context['parsed'][0][key($context['parsed'][0])] .= $token; return; } } else { static::pushPartial($context, Token::toString($context['currentToken'])); switch ($context['currentToken'][Token::POS_OP]) { case '#*': array_unshift($context['inlinepartial'], ''); break; case '#>': array_unshift($context['partialblock'], ''); break; } } $context['parsed'][0][] = $token; } /** * push current token into the section stack * * @param array<string,array|string|integer> $context Current context * @param string $operation operation string * @param array<boolean|integer|string|array> $vars parsed arguments list */ protected static function pushStack(&$context, $operation, $vars) { list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]); $context['stack'][] = $context['currentToken'][Token::POS_INNERTAG]; $context['stack'][] = Expression::toString($levels, $spvar, $var); $context['stack'][] = $operation; $context['level']++; } /** * Verify delimiters and operators * * @param string[] $token detected handlebars {{ }} token * @param array<string,array|string|integer> $context current compile context * * @return boolean|null Return true when invalid * * @expect null when input array_fill(0, 11, ''), array() * @expect null when input array(0, 0, 0, 0, 0, '{{', '#', '...', '}}'), array() * @expect true when input array(0, 0, 0, 0, 0, '{', '#', '...', '}'), array() */ protected static function delimiter($token, &$context) { // {{ }}} or {{{ }} are invalid if (strlen($token[Token::POS_BEGINRAW]) !== strlen($token[Token::POS_ENDRAW])) { $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_BEGINRAW => '', Token::POS_ENDRAW => '')) . ' or ' . Token::toString($token, array(Token::POS_BEGINRAW => '{', Token::POS_ENDRAW => '}')) . '?'; return true; } // {{{# }}} or {{{! }}} or {{{/ }}} or {{{^ }}} are invalid. if ((strlen($token[Token::POS_BEGINRAW]) == 1) && $token[Token::POS_OP] && ($token[Token::POS_OP] !== '&')) { $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_BEGINRAW => '', Token::POS_ENDRAW => '')) . ' ?'; return true; } } /** * Verify operators * * @param string $operator the operator string * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean|integer|null Return true when invalid or detected * * @expect null when input '', array(), array() * @expect 2 when input '^', array('usedFeature' => array('isec' => 1), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'elselvl' => array(), 'flags' => array('spvar' => 0), 'elsechain' => false, 'helperresolver' => 0), array(array('foo')) * @expect true when input '/', array('stack' => array('[with]', '#'), 'level' => 1, 'currentToken' => array(0,0,0,0,0,0,0,'with'), 'flags' => array('nohbh' => 0)), array(array()) * @expect 4 when input '#', array('usedFeature' => array('sec' => 3), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('x')) * @expect 5 when input '#', array('usedFeature' => array('if' => 4), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('if')) * @expect 6 when input '#', array('usedFeature' => array('with' => 5), 'level' => 0, 'flags' => array('nohbh' => 0, 'runpart' => 0, 'spvar' => 0), 'currentToken' => array(0,0,0,0,0,0,0,0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('with')) * @expect 7 when input '#', array('usedFeature' => array('each' => 6), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('each')) * @expect 8 when input '#', array('usedFeature' => array('unless' => 7), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('unless')) * @expect 9 when input '#', array('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 8), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array()), array(array('abc')) * @expect 11 when input '#', array('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 10), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array()), array(array('abc')) * @expect true when input '>', array('partialresolver' => false, 'usedFeature' => array('partial' => 7), 'level' => 0, 'flags' => array('skippartial' => 0, 'runpart' => 0, 'spvar' => 0), 'currentToken' => array(0,0,0,0,0,0,0,0), 'elsechain' => false, 'elselvl' => array()), array('test') */ protected static function operator($operator, &$context, &$vars) { switch ($operator) { case '#*': if (!$context['compile']) { $context['stack'][] = count($context['parsed'][0]) + ($context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE] === '' ? 0 : 1); static::pushStack($context, '#*', $vars); } return static::inline($context, $vars); case '#>': if (!$context['compile']) { $context['stack'][] = count($context['parsed'][0]) + ($context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE] === '' ? 0 : 1); $vars[Parser::PARTIALBLOCK] = ++$context['usedFeature']['pblock']; static::pushStack($context, '#>', $vars); } // no break case '>': return static::partial($context, $vars); case '^': if (!isset($vars[0][0])) { if (!$context['flags']['else']) { $context['error'][] = 'Do not support {{^}}, you should do compile with LightnCandy::FLAG_ELSE flag'; return; } else { return static::doElse($context, $vars); } } static::doElseChain($context); if (static::isBlockHelper($context, $vars)) { static::pushStack($context, '#', $vars); return static::blockCustomHelper($context, $vars, true); } static::pushStack($context, '^', $vars); return static::invertedSection($context, $vars); case '/': $r = static::blockEnd($context, $vars); if ($r !== Token::POS_BACKFILL) { array_pop($context['stack']); array_pop($context['stack']); array_pop($context['stack']); } return $r; case '#': static::doElseChain($context); static::pushStack($context, '#', $vars); if (static::isBlockHelper($context, $vars)) { return static::blockCustomHelper($context, $vars); } return static::blockBegin($context, $vars); } } /** * validate inline partial begin token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean|null Return true when inline partial ends */ protected static function inlinePartial(&$context, $vars) { $ended = false; if ($context['currentToken'][Token::POS_OP] === '/') { if (static::blockEnd($context, $vars, '#*') !== null) { $context['usedFeature']['inlpartial']++; $tmpl = array_shift($context['inlinepartial']) . $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE]; $c = $context['stack'][count($context['stack']) - 4]; $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1); $P = &$context['parsed'][0][$c]; if (isset($P[1][1][0])) { $context['usedPartial'][$P[1][1][0]] = $tmpl; $P[1][0][0] = Partial::compileDynamic($context, $P[1][1][0]); } $ended = true; } } return $ended; } /** * validate partial block token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean|null Return true when partial block ends */ protected static function partialBlock(&$context, $vars) { $ended = false; if ($context['currentToken'][Token::POS_OP] === '/') { if (static::blockEnd($context, $vars, '#>') !== null) { $c = $context['stack'][count($context['stack']) - 4]; $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1); $found = Partial::resolve($context, $vars[0][0]) !== null; $v = $found ? "@partial-block{$context['parsed'][0][$c][1][Parser::PARTIALBLOCK]}" : "{$vars[0][0]}"; if (count($context['partialblock']) == 1) { $tmpl = $context['partialblock'][0] . $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE]; if ($found) { $context['partials'][$v] = $tmpl; } $context['usedPartial'][$v] = $tmpl; Partial::compileDynamic($context, $v); if ($found) { Partial::read($context, $vars[0][0]); } } array_shift($context['partialblock']); $ended = true; } } return $ended; } /** * handle else chain * * @param array<string,array|string|integer> $context current compile context */ protected static function doElseChain(&$context) { if ($context['elsechain']) { $context['elsechain'] = false; } else { array_unshift($context['elselvl'], array()); } } /** * validate block begin token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean Return true always */ protected static function blockBegin(&$context, $vars) { switch ((isset($vars[0][0]) && is_string($vars[0][0])) ? $vars[0][0] : null) { case 'with': return static::with($context, $vars); case 'each': return static::section($context, $vars, true); case 'unless': return static::unless($context, $vars); case 'if': return static::doIf($context, $vars); default: return static::section($context, $vars); } } /** * validate builtin helpers * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list */ protected static function builtin(&$context, $vars) { if ($context['flags']['nohbh']) { if (isset($vars[1][0])) { $context['error'][] = "Do not support {{#{$vars[0][0]} var}} because you compile with LightnCandy::FLAG_NOHBHELPERS flag"; } } else { if (count($vars) < 2) { $context['error'][] = "No argument after {{#{$vars[0][0]}}} !"; } } $context['usedFeature'][$vars[0][0]]++; } /** * validate section token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $isEach the section is #each * * @return boolean Return true always */ protected static function section(&$context, $vars, $isEach = false) { if ($isEach) { static::builtin($context, $vars); } else { if ((count($vars) > 1) && !$context['flags']['lambda']) { $context['error'][] = "Custom helper not found: {$vars[0][0]} in " . Token::toString($context['currentToken']) . ' !'; } $context['usedFeature']['sec']++; } return true; } /** * validate with token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean Return true always */ protected static function with(&$context, $vars) { static::builtin($context, $vars); return true; } /** * validate unless token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean Return true always */ protected static function unless(&$context, $vars) { static::builtin($context, $vars); return true; } /** * validate if token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean Return true always */ protected static function doIf(&$context, $vars) { static::builtin($context, $vars); return true; } /** * validate block custom helper token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $inverted the logic will be inverted * * @return integer|null Return number of used custom helpers */ protected static function blockCustomHelper(&$context, $vars, $inverted = false) { if (is_string($vars[0][0])) { if (static::resolveHelper($context, $vars)) { return ++$context['usedFeature']['helper']; } } } /** * validate inverted section * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return integer Return number of inverted sections */ protected static function invertedSection(&$context, $vars) { return ++$context['usedFeature']['isec']; } /** * Return compiled PHP code for a handlebars block end token * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param string|null $match should also match to this operator * * @return boolean|integer Return true when required block ended, or Token::POS_BACKFILL when backfill happened. */ protected static function blockEnd(&$context, &$vars, $match = null) { $c = count($context['stack']) - 2; $pop = ($c >= 0) ? $context['stack'][$c + 1] : ''; if (($match !== null) && ($match !== $pop)) { return; } // if we didn't match our $pop, we didn't actually do a level, so only subtract a level here $context['level']--; $pop2 = ($c >= 0) ? $context['stack'][$c]: ''; switch ($context['currentToken'][Token::POS_INNERTAG]) { case 'with': if (!$context['flags']['nohbh']) { if ($pop2 !== '[with]') { $context['error'][] = 'Unexpect token: {{/with}} !'; return; } } return true; } switch ($pop) { case '#': case '^': $elsechain = array_shift($context['elselvl']); if (isset($elsechain[0])) { // we need to repeat a level due to else chains: {{else if}} $context['level']++; $context['currentToken'][Token::POS_RSPACE] = $context['currentToken'][Token::POS_BACKFILL] = '{{/' . implode('}}{{/', $elsechain) . '}}' . Token::toString($context['currentToken']) . $context['currentToken'][Token::POS_RSPACE]; return Token::POS_BACKFILL; } // no break case '#>': case '#*': list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]); $v = Expression::toString($levels, $spvar, $var); if ($pop2 !== $v) { $context['error'][] = 'Unexpect token ' . Token::toString($context['currentToken']) . " ! Previous token {{{$pop}$pop2}} is not closed"; return; } return true; default: $context['error'][] = 'Unexpect token: ' . Token::toString($context['currentToken']) . ' !'; return; } } /** * handle delimiter change * * @param array<string,array|string|integer> $context current compile context * * @return boolean|null Return true when delimiter changed */ protected static function isDelimiter(&$context) { if (preg_match('/^=\s*([^ ]+)\s+([^ ]+)\s*=$/', $context['currentToken'][Token::POS_INNERTAG], $matched)) { $context['usedFeature']['delimiter']++; Parser::setDelimiter($context, $matched[1], $matched[2]); return true; } } /** * handle raw block * * @param string[] $token detected handlebars {{ }} token * @param array<string,array|string|integer> $context current compile context * * @return boolean|null Return true when in rawblock mode */ protected static function rawblock(&$token, &$context) { $inner = $token[Token::POS_INNERTAG]; trim($inner); // skip parse when inside raw block if ($context['rawblock'] && !(($token[Token::POS_BEGINRAW] === '{{') && ($token[Token::POS_OP] === '/') && ($context['rawblock'] === $inner))) { return true; } $token[Token::POS_INNERTAG] = $inner; // Handle raw block if ($token[Token::POS_BEGINRAW] === '{{') { if ($token[Token::POS_ENDRAW] !== '}}') { $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_ENDRAW => '}}')) . ' ?'; } if ($context['rawblock']) { Parser::setDelimiter($context); $context['rawblock'] = false; } else { if ($token[Token::POS_OP]) { $context['error'][] = "Wrong raw block begin with " . Token::toString($token) . ' ! Remove "' . $token[Token::POS_OP] . '" to fix this issue.'; } $context['rawblock'] = $token[Token::POS_INNERTAG]; Parser::setDelimiter($context); $token[Token::POS_OP] = '#'; } $token[Token::POS_ENDRAW] = '}}'; } } /** * handle comment * * @param string[] $token detected handlebars {{ }} token * @param array<string,array|string|integer> $context current compile context * * @return boolean|null Return true when is comment */ protected static function comment(&$token, &$context) { if ($token[Token::POS_OP] === '!') { $context['usedFeature']['comment']++; return true; } } /** * Collect handlebars usage information, detect template error. * * @param string[] $token detected handlebars {{ }} token * @param array<string,array|string|integer> $context current compile context * * @return string|array<string,array|string|integer>|null $token string when rawblock; array when valid token require to be compiled, null when skip the token. */ protected static function token(&$token, &$context) { $context['currentToken'] = &$token; if (static::rawblock($token, $context)) { return Token::toString($token); } if (static::delimiter($token, $context)) { return; } if (static::isDelimiter($context)) { static::spacing($token, $context); return; } if (static::comment($token, $context)) { static::spacing($token, $context); return; } list($raw, $vars) = Parser::parse($token, $context); // Handle spacing (standalone tags, partial indent) static::spacing($token, $context, (($token[Token::POS_OP] === '') || ($token[Token::POS_OP] === '&')) && (!$context['flags']['else'] || !isset($vars[0][0]) || ($vars[0][0] !== 'else')) || ($context['flags']['nostd'] > 0)); $inlinepartial = static::inlinePartial($context, $vars); $partialblock = static::partialBlock($context, $vars); if ($partialblock || $inlinepartial) { $context['stack'] = array_slice($context['stack'], 0, -4); static::pushPartial($context, $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE] . Token::toString($context['currentToken'])); $context['currentToken'][Token::POS_LOTHER] = ''; $context['currentToken'][Token::POS_LSPACE] = ''; return; } if (static::operator($token[Token::POS_OP], $context, $vars)) { return isset($token[Token::POS_BACKFILL]) ? null : array($raw, $vars); } if (count($vars) == 0) { return $context['error'][] = 'Wrong variable naming in ' . Token::toString($token); } if (!isset($vars[0])) { return $context['error'][] = 'Do not support name=value in ' . Token::toString($token) . ', you should use it after a custom helper.'; } $context['usedFeature'][$raw ? 'raw' : 'enc']++; foreach ($vars as $var) { if (!isset($var[0]) || ($var[0] === 0)) { if ($context['level'] == 0) { $context['usedFeature']['rootthis']++; } $context['usedFeature']['this']++; } } if (!isset($vars[0][0])) { return array($raw, $vars); } if (($vars[0][0] === 'else') && $context['flags']['else']) { static::doElse($context, $vars); return array($raw, $vars); } if (!static::helper($context, $vars)) { static::lookup($context, $vars); static::log($context, $vars); } return array($raw, $vars); } /** * Return 1 or larger number when else token detected * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return integer Return 1 or larger number when else token detected */ protected static function doElse(&$context, $vars) { if ($context['level'] == 0) { $context['error'][] = '{{else}} only valid in if, unless, each, and #section context'; } if (isset($vars[1][0])) { $token = $context['currentToken']; $context['currentToken'][Token::POS_INNERTAG] = 'else'; $context['currentToken'][Token::POS_RSPACE] = "{{#{$vars[1][0]} " . preg_replace('/^\\s*else\\s+' . $vars[1][0] . '\\s*/', '', $token[Token::POS_INNERTAG]) . '}}' . $context['currentToken'][Token::POS_RSPACE]; array_unshift($context['elselvl'][0], $vars[1][0]); $context['elsechain'] = true; } return ++$context['usedFeature']['else']; } /** * Return true when this is {{log ...}} * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean|null Return true when it is custom helper */ public static function log(&$context, $vars) { if (isset($vars[0][0]) && ($vars[0][0] === 'log')) { if (!$context['flags']['nohbh']) { if (count($vars) < 2) { $context['error'][] = "No argument after {{log}} !"; } $context['usedFeature']['log']++; return true; } } } /** * Return true when this is {{lookup ...}} * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean|null Return true when it is custom helper */ public static function lookup(&$context, $vars) { if (isset($vars[0][0]) && ($vars[0][0] === 'lookup')) { if (!$context['flags']['nohbh']) { if (count($vars) < 2) { $context['error'][] = "No argument after {{lookup}} !"; } elseif (count($vars) < 3) { $context['error'][] = "{{lookup}} requires 2 arguments !"; } $context['usedFeature']['lookup']++; return true; } } } /** * Return true when the name is listed in helper table * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * @param boolean $checkSubexp true when check for subexpression * * @return boolean Return true when it is custom helper */ public static function helper(&$context, $vars, $checkSubexp = false) { if (static::resolveHelper($context, $vars)) { $context['usedFeature']['helper']++; return true; } if ($checkSubexp) { switch ($vars[0][0]) { case 'if': case 'unless': case 'with': case 'each': case 'lookup': return $context['flags']['nohbh'] ? false : true; } } return false; } /** * use helperresolver to resolve helper, return true when helper founded * * @param array<string,array|string|integer> $context Current context of compiler progress. * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean $found helper exists or not */ public static function resolveHelper(&$context, &$vars) { if (count($vars[0]) !== 1) { return false; } if (isset($context['helpers'][$vars[0][0]])) { return true; } if ($context['helperresolver']) { $helper = $context['helperresolver']($context, $vars[0][0]); if ($helper) { $context['helpers'][$vars[0][0]] = $helper; return true; } } return false; } /** * detect for block custom helper * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean|null Return true when this token is block custom helper */ protected static function isBlockHelper($context, $vars) { if (!isset($vars[0][0])) { return; } if (!static::resolveHelper($context, $vars)) { return; } return true; } /** * validate inline partial * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return boolean Return true always */ protected static function inline(&$context, $vars) { if (!$context['flags']['runpart']) { $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag"; } if (!isset($vars[0][0]) || ($vars[0][0] !== 'inline')) { $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, now we only support {{#*inline \"partialName\"}}template...{{/inline}}"; } if (!isset($vars[1][0])) { $context['error'][] = "Error in {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}: inline require 1 argument for partial name!"; } return true; } /** * validate partial * * @param array<string,array|string|integer> $context current compile context * @param array<boolean|integer|string|array> $vars parsed arguments list * * @return integer|boolean Return 1 or larger number for runtime partial, return true for other case */ protected static function partial(&$context, $vars) { if (Parser::isSubExp($vars[0])) { if ($context['flags']['runpart']) { return $context['usedFeature']['dynpartial']++; } else { $context['error'][] = "You use dynamic partial name as '{$vars[0][2]}', this only works with option FLAG_RUNTIMEPARTIAL enabled"; return true; } } else { if ($context['currentToken'][Token::POS_OP] !== '#>') { Partial::read($context, $vars[0][0]); } } if (!$context['flags']['runpart']) { $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0; if ($named || (count($vars) > 1)) { $context['error'][] = "Do not support {{>{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag"; } } return true; } /** * Modify $token when spacing rules matched. * * @param array<string> $token detected handlebars {{ }} token * @param array<string,array|string|integer> $context current compile context * @param boolean $nost do not do stand alone logic * * @return string|null Return compiled code segment for the token */ protected static function spacing(&$token, &$context, $nost = false) { // left line change detection $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[Token::POS_LSPACE], $lmatch); $ind = $lsp ? $lmatch[3] : $token[Token::POS_LSPACE]; // right line change detection $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[Token::POS_RSPACE], $rmatch); $st = true; // setup ahead flag $ahead = $context['tokens']['ahead']; $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[Token::POS_RSPACE] . $token[Token::POS_ROTHER]); // reset partial indent $context['tokens']['partialind'] = ''; // same tags in the same line , not standalone if (!$lsp && $ahead) { $st = false; } if ($nost) { $st = false; } // not standalone because other things in the same line ahead if ($token[Token::POS_LOTHER] && !$token[Token::POS_LSPACE]) { $st = false; } // not standalone because other things in the same line behind if ($token[Token::POS_ROTHER] && !$token[Token::POS_RSPACE]) { $st = false; } if ($st && ( ($lsp && $rsp) // both side cr || ($rsp && !$token[Token::POS_LOTHER]) // first line without left || ($lsp && !$token[Token::POS_ROTHER]) // final line )) { // handle partial if ($token[Token::POS_OP] === '>') { if (!$context['flags']['noind']) { $context['tokens']['partialind'] = $token[Token::POS_LSPACECTL] ? '' : $ind; $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : ''); } } else { $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : ''); } $token[Token::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : ''; } // Handle space control. if ($token[Token::POS_LSPACECTL]) { $token[Token::POS_LSPACE] = ''; } if ($token[Token::POS_RSPACECTL]) { $token[Token::POS_RSPACE] = ''; } } } PK ! �Q�$ �$ lightncandy/src/Exporter.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to keep LightnCandy Exporter * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy major static class */ class Exporter { /** * Get PHP code string from a closure of function as string * * @param array<string,array|string|integer> $context current compile context * @param object $closure Closure object * * @return string * * @expect 'function($a) {return;}' when input array('flags' => array('standalone' => 0)), function ($a) {return;} * @expect 'function($a) {return;}' when input array('flags' => array('standalone' => 0)), function ($a) {return;} */ protected static function closure($context, $closure) { if (is_string($closure) && preg_match('/(.+)::(.+)/', $closure, $matched)) { $ref = new \ReflectionMethod($matched[1], $matched[2]); } else { $ref = new \ReflectionFunction($closure); } $meta = static::getMeta($ref); return preg_replace('/^.*?function(\s+[^\s\\(]+?)?\s*\\((.+)\\}.*?\s*$/s', 'function($2}', static::replaceSafeString($context, $meta['code'])); } /** * Export required custom helper functions * * @param array<string,array|string|integer> $context current compile context * * @return string */ public static function helpers($context) { $ret = ''; foreach ($context['helpers'] as $name => $func) { if (!isset($context['usedCount']['helpers'][$name])) { continue; } if ((is_object($func) && ($func instanceof \Closure)) || ($context['flags']['exhlp'] == 0)) { $ret .= (" '$name' => " . static::closure($context, $func) . ",\n"); continue; } $ret .= " '$name' => '$func',\n"; } return "array($ret)"; } /** * Replace SafeString class with alias class name * * @param array<string,array|string|integer> $context current compile context * @param string $str the PHP code to be replaced * * @return string */ protected static function replaceSafeString($context, $str) { return $context['flags']['standalone'] ? str_replace($context['safestring'], $context['safestringalias'], $str) : $str; } /** * Get methods from ReflectionClass * * @param array<string,array|string|integer> $context current compile context * @param \ReflectionClass $class instance of the ReflectionClass * * @return array */ public static function getClassMethods($context, $class) { $methods = array(); foreach ($class->getMethods() as $method) { $meta = static::getMeta($method); $methods[$meta['name']] = static::scanDependency($context, preg_replace('/public static function (.+)\\(/', "function {$context['funcprefix']}\$1(", $meta['code']), $meta['code']); } return $methods; } /** * Get statics code from ReflectionClass * * @param \ReflectionClass $class instance of the ReflectionClass * * @return string */ public static function getClassStatics($class) { $ret = ''; foreach ($class->getStaticProperties() as $name => $value) { $ret .= " public static \${$name} = " . var_export($value, true) . ";\n"; } return $ret; } /** * Get metadata from ReflectionObject * * @param object $refobj instance of the ReflectionObject * * @return array */ public static function getMeta($refobj) { $fname = $refobj->getFileName(); $lines = file_get_contents($fname); $file = new \SplFileObject($fname); $start = $refobj->getStartLine() - 2; $end = $refobj->getEndLine() - 1; if (version_compare(\PHP_VERSION, '8.0.0') >= 0) { $start++; $end++; } $file->seek($start); $spos = $file->ftell(); $file->seek($end); $epos = $file->ftell(); unset($file); return array( 'name' => $refobj->getName(), 'code' => substr($lines, $spos, $epos - $spos) ); } /** * Export SafeString class as string * * @param array<string,array|string|integer> $context current compile context * * @return string */ public static function safestring($context) { $class = new \ReflectionClass($context['safestring']); return array_reduce(static::getClassMethods($context, $class), function ($in, $cur) { return $in . $cur[2]; }, "if (!class_exists(\"" . addslashes($context['safestringalias']) . "\")) {\nclass {$context['safestringalias']} {\n" . static::getClassStatics($class)) . "}\n}\n"; } /** * Export StringObject class as string * * @param array<string,array|string|integer> $context current compile context * * @return string */ public static function stringobject($context) { if ($context['flags']['standalone'] == 0) { return 'use \\LightnCandy\\StringObject as StringObject;'; } $class = new \ReflectionClass('\\LightnCandy\\StringObject'); $meta = static::getMeta($class); return "if (!class_exists(\"StringObject\")) {\n{$meta['code']}}\n"; } /** * Export required standalone Runtime methods * * @param array<string,array|string|integer> $context current compile context * * @return string */ public static function runtime($context) { $class = new \ReflectionClass($context['runtime']); $ret = ''; $methods = static::getClassMethods($context, $class); $exports = array_keys($context['usedCount']['runtime']); while (true) { if (array_sum(array_map(function ($name) use (&$exports, $methods) { $n = 0; foreach ($methods[$name][1] as $child => $count) { if (!in_array($child, $exports)) { $exports[] = $child; $n++; } } return $n; }, $exports)) == 0) { break; } } foreach ($exports as $export) { $ret .= ($methods[$export][0] . "\n"); } return $ret; } /** * Export Runtime constants * * @param array<string,array|string|integer> $context current compile context * * @return string */ public static function constants($context) { if ($context['flags']['standalone'] == 0) { return 'array()'; } $class = new \ReflectionClass($context['runtime']); $constants = $class->getConstants(); $ret = " array(\n"; foreach ($constants as $name => $value) { $ret .= " '$name' => ". (is_string($value) ? "'$value'" : $value) . ",\n"; } $ret .= " )"; return $ret; } /** * Scan for required standalone functions * * @param array<string,array|string|integer> $context current compile context * @param string $code patched PHP code string of the method * @param string $ocode original PHP code string of the method * * @return array<string|array> list of converted code and children array */ protected static function scanDependency($context, $code, $ocode) { $child = array(); $code = preg_replace_callback('/static::(\w+?)\s*\(/', function ($matches) use ($context, &$child) { if (!isset($child[$matches[1]])) { $child[$matches[1]] = 0; } $child[$matches[1]]++; return "{$context['funcprefix']}{$matches[1]}("; }, $code); // replace the constants $code = preg_replace('/static::([A-Z0-9_]+)/', "\$cx['constants']['$1']", $code); // compress space $code = preg_replace('/ /', ' ', $code); return array(static::replaceSafeString($context, $code), $child, $ocode); } } PK ! C��-�v �v lightncandy/src/Parser.phpnu �Iw�� <?php /* MIT License Copyright 2013-2021 Zordius Chen. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Origin: https://github.com/zordius/lightncandy */ /** * file to keep LightnCandy Parser * * @package LightnCandy * @author Zordius <zordius@gmail.com> */ namespace LightnCandy; /** * LightnCandy Parser */ class Parser extends Token { // Compile time error handling flags const BLOCKPARAM = 9999; const PARTIALBLOCK = 9998; const LITERAL = -1; const SUBEXP = -2; /** * Get partial block id and fix the variable list * * @param array<boolean|integer|string|array> $vars parsed token * * @return integer Return partial block id * */ public static function getPartialBlock(&$vars) { if (isset($vars[static::PARTIALBLOCK])) { $id = $vars[static::PARTIALBLOCK]; unset($vars[static::PARTIALBLOCK]); return $id; } return 0; } /** * Get block params and fix the variable list * * @param array<boolean|integer|string|array> $vars parsed token * * @return array<string>|null Return list of block params or null * */ public static function getBlockParams(&$vars) { if (isset($vars[static::BLOCKPARAM])) { $list = $vars[static::BLOCKPARAM]; unset($vars[static::BLOCKPARAM]); return $list; } } /** * Return array presentation for a literal * * @param string $name variable name. * @param boolean $asis keep the name as is or not * @param boolean $quote add single quote or not * * @return array<integer|string> Return variable name array * */ protected static function getLiteral($name, $asis, $quote = false) { return $asis ? array($name) : array(static::LITERAL, $quote ? "'$name'" : $name); } /** * Return array presentation for an expression * * @param string $v analyzed expression names. * @param array<string,array|string|integer> $context Current compile content. * @param integer $pos expression position * * @return array<integer,string> Return variable name array * * @expect array('this') when input 'this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 0)), 0 * @expect array() when input 'this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1)), 0 * @expect array(1) when input '..', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(1) when input '../', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(1) when input '../.', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(1) when input '../this', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(1, 'a') when input '../a', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(2, 'a', 'b') when input '../../a.b', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(2, '[a]', 'b') when input '../../[a].b', array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(2, 'a', 'b') when input '../../[a].b', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(0, 'id') when input 'this.id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array('this', 'id') when input 'this.id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(0, 'id') when input './id', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 0 * @expect array(\LightnCandy\Parser::LITERAL, '\'a.b\'') when input '"a.b"', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1 * @expect array(\LightnCandy\Parser::LITERAL, '123') when input '123', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1 * @expect array(\LightnCandy\Parser::LITERAL, 'null') when input 'null', array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)), 1 */ protected static function getExpression($v, &$context, $pos) { $asis = ($pos === 0); // handle number if (is_numeric($v)) { return static::getLiteral(strval(1 * $v), $asis); } // handle double quoted string if (preg_match('/^"(.*)"$/', $v, $matched)) { return static::getLiteral(preg_replace('/([^\\\\])\\\\\\\\"/', '$1"', preg_replace('/^\\\\\\\\"/', '"', $matched[1])), $asis, true); } // handle single quoted string if (preg_match('/^\\\\\'(.*)\\\\\'$/', $v, $matched)) { return static::getLiteral($matched[1], $asis, true); } // handle boolean, null and undefined if (preg_match('/^(true|false|null|undefined)$/', $v)) { return static::getLiteral($v, $asis); } $ret = array(); $levels = 0; // handle .. if ($v === '..') { $v = '../'; } // Trace to parent for ../ N times $v = preg_replace_callback('/\\.\\.\\//', function () use (&$levels) { $levels++; return ''; }, trim($v)); // remove ./ in path $v = preg_replace('/\\.\\//', '', $v, -1, $scoped); $strp = (($pos !== 0) && $context['flags']['strpar']); if ($levels && !$strp) { $ret[] = $levels; if (!$context['flags']['parent']) { $context['error'][] = 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag'; } $context['usedFeature']['parent'] ++; } if ($context['flags']['advar'] && preg_match('/\\]/', $v)) { preg_match_all(static::VARNAME_SEARCH, $v, $matchedall); } else { preg_match_all('/([^\\.\\/]+)/', $v, $matchedall); } if ($v !== '.') { $vv = implode('.', $matchedall[1]); if (strlen($v) !== strlen($vv)) { $context['error'][] = "Unexpected charactor in '$v' ! (should it be '$vv' ?)"; } } foreach ($matchedall[1] as $m) { if ($context['flags']['advar'] && substr($m, 0, 1) === '[') { $ret[] = substr($m, 1, -1); } elseif ((!$context['flags']['this'] || ($m !== 'this')) && ($m !== '.')) { $ret[] = $m; } else { $scoped++; } } if ($strp) { return array(static::LITERAL, "'" . implode('.', $ret) . "'"); } if (($scoped > 0) && ($levels === 0) && (count($ret) > 0)) { array_unshift($ret, 0); } return $ret; } /** * Parse the token and return parsed result. * * @param array<string> $token preg_match results * @param array<string,array|string|integer> $context current compile context * * @return array<boolean|integer|array> Return parsed result * * @expect array(false, array(array())) when input array(0,0,0,0,0,0,0,''), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(true, array(array())) when input array(0,0,0,'{{',0,'{',0,''), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(true, array(array())) when input array(0,0,0,0,0,0,0,''), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 1), 'rawblock' => false) * @expect array(false, array(array('a'))) when input array(0,0,0,0,0,0,0,'a'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array('b'))) when input array(0,0,0,0,0,0,0,'a b'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array('"b'), array('c"'))) when input array(0,0,0,0,0,0,0,'a "b c"'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array(-1, '\'b c\''))) when input array(0,0,0,0,0,0,0,'a "b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,0,'a [b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), 'q' => array('b c'))) when input array(0,0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array('q=[b c'))) when input array(0,0,0,0,0,0,0,'a [q=[b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), 'q' => array('[b'), array('c]'))) when input array(0,0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), 'q' => array('b'), array('c'))) when input array(0,0,0,0,0,0,0,'a [q]=b c'), array('flags' => array('strpar' => 0, 'advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), 'q' => array(-1, '\'b c\''))) when input array(0,0,0,0,0,0,0,'a q="b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array(-2, array(array('foo'), array('bar')), '(foo bar)'))) when input array(0,0,0,0,0,0,0,'(foo bar)'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 1, 'lambda' => 0), 'ops' => array('seperator' => ''), 'usedFeature' => array('subexp' => 0), 'rawblock' => false) * @expect array(false, array(array('foo'), array("'=='"), array('bar'))) when input array(0,0,0,0,0,0,0,"foo '==' bar"), array('flags' => array('strpar' => 0, 'advar' => 1, 'namev' => 1, 'noesc' => 0, 'this' => 0), 'rawblock' => false) * @expect array(false, array(array(-2, array(array('foo'), array('bar')), '( foo bar)'))) when input array(0,0,0,0,0,0,0,'( foo bar)'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 1, 'lambda' => 0), 'ops' => array('seperator' => ''), 'usedFeature' => array('subexp' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array(-1, '\' b c\''))) when input array(0,0,0,0,0,0,0,'a " b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), 'q' => array(-1, '\' b c\''))) when input array(0,0,0,0,0,0,0,'a q=" b c"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('foo'), array(-1, "' =='"), array('bar'))) when input array(0,0,0,0,0,0,0,"foo \' ==\' bar"), array('flags' => array('strpar' => 0, 'advar' => 1, 'namev' => 1, 'noesc' => 0, 'this' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), array(' b c'))) when input array(0,0,0,0,0,0,0,'a [ b c]'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array(array('a'), 'q' => array(-1, "' d e'"))) when input array(0,0,0,0,0,0,0,"a q=\' d e\'"), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'rawblock' => false) * @expect array(false, array('q' => array(-2, array(array('foo'), array('bar')), '( foo bar)'))) when input array(0,0,0,0,0,0,0,'q=( foo bar)'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false, 'helperresolver' => 0) * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','foo'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false) * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','"foo"'), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false) * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','[foo] '), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false) * @expect array(false, array(array('foo'))) when input array(0,0,0,0,0,0,'>','\\\'foo\\\''), array('flags' => array('strpar' => 0, 'advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 0, 'lambda' => 0), 'usedFeature' => array('subexp' => 0), 'ops' => array('seperator' => 0), 'rawblock' => false) */ public static function parse(&$token, &$context) { $vars = static::analyze($token[static::POS_INNERTAG], $context); if ($token[static::POS_OP] === '>') { $fn = static::getPartialName($vars); } elseif ($token[static::POS_OP] === '#*') { $fn = static::getPartialName($vars, 1); } $avars = static::advancedVariable($vars, $context, static::toString($token)); if (isset($fn) && ($fn !== null)) { if ($token[static::POS_OP] === '>') { $avars[0] = $fn; } elseif ($token[static::POS_OP] === '#*') { $avars[1] = $fn; } } return array(($token[static::POS_BEGINRAW] === '{') || ($token[static::POS_OP] === '&') || $context['flags']['noesc'] || $context['rawblock'], $avars); } /** * Get partial name from "foo" or [foo] or \'foo\' * * @param array<boolean|integer|array> $vars parsed token * @param integer $pos position of partial name * * @return array<string>|null Return one element partial name array * * @expect null when input array() * @expect array('foo') when input array('foo') * @expect array('foo') when input array('"foo"') * @expect array('foo') when input array('[foo]') * @expect array('foo') when input array("\\'foo\\'") * @expect array('foo') when input array(0, 'foo'), 1 */ public static function getPartialName(&$vars, $pos = 0) { if (!isset($vars[$pos])) { return; } return preg_match(SafeString::IS_SUBEXP_SEARCH, $vars[$pos]) ? null : array(preg_replace('/^("(.+)")|(\\[(.+)\\])|(\\\\\'(.+)\\\\\')$/', '$2$4$6', $vars[$pos])); } /** * Parse a subexpression then return parsed result. * * @param string $expression the full string of a sub expression * @param array<string,array|string|integer> $context current compile context * * @return array<boolean|integer|array> Return parsed result * * @expect array(\LightnCandy\Parser::SUBEXP, array(array('a'), array('b')), '(a b)') when input '(a b)', array('usedFeature' => array('subexp' => 0), 'flags' => array('advar' => 0, 'namev' => 0, 'this' => 0, 'exhlp' => 1, 'strpar' => 0)) */ public static function subexpression($expression, &$context) { $context['usedFeature']['subexp']++; $vars = static::analyze(substr($expression, 1, -1), $context); $avars = static::advancedVariable($vars, $context, $expression); if (isset($avars[0][0]) && !$context['flags']['exhlp']) { if (!Validator::helper($context, $avars, true)) { $context['error'][] = "Can not find custom helper function defination {$avars[0][0]}() !"; } } return array(static::SUBEXP, $avars, $expression); } /** * Check a parsed result is a subexpression or not * * @param array<string|integer|array> $var * * @return boolean return true when input is a subexpression * * @expect false when input 0 * @expect false when input array() * @expect false when input array(\LightnCandy\Parser::SUBEXP, 0) * @expect false when input array(\LightnCandy\Parser::SUBEXP, 0, 0) * @expect false when input array(\LightnCandy\Parser::SUBEXP, 0, '', 0) * @expect true when input array(\LightnCandy\Parser::SUBEXP, 0, '') */ public static function isSubExp($var) { return is_array($var) && (count($var) === 3) && ($var[0] === static::SUBEXP) && is_string($var[2]); } /** * Analyze parsed token for advanved variables. * * @param array<boolean|integer|array> $vars parsed token * @param array<string,array|string|integer> $context current compile context * @param string $token original token * * @return array<boolean|integer|array> Return parsed result * * @expect array(array('this')) when input array('this'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0,)), 0 * @expect array(array()) when input array('this'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 1)), 0 * @expect array(array('a')) when input array('a'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0, 'strpar' => 0)), 0 * @expect array(array('a'), array('b')) when input array('a', 'b'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0, 'strpar' => 0)), 0 * @expect array('a' => array('b')) when input array('a=b'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0, 'strpar' => 0)), 0 * @expect array('fo o' => array(\LightnCandy\Parser::LITERAL, '123')) when input array('[fo o]=123'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0)), 0 * @expect array('fo o' => array(\LightnCandy\Parser::LITERAL, '\'bar\'')) when input array('[fo o]="bar"'), array('flags' => array('advar' => 1, 'namev' => 1, 'this' => 0)), 0 */ protected static function advancedVariable($vars, &$context, $token) { $ret = array(); $i = 0; foreach ($vars as $idx => $var) { // handle (...) if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) { $ret[$i] = static::subexpression($var, $context); $i++; continue; } // handle |...| if (preg_match(SafeString::IS_BLOCKPARAM_SEARCH, $var, $matched)) { $ret[static::BLOCKPARAM] = explode(' ', $matched[1]); continue; } if ($context['flags']['namev']) { if (preg_match('/^((\\[([^\\]]+)\\])|([^=^["\']+))=(.+)$/', $var, $m)) { if (!$context['flags']['advar'] && $m[3]) { $context['error'][] = "Wrong argument name as '[$m[3]]' in $token ! You should fix your template or compile with LightnCandy::FLAG_ADVARNAME flag."; } $idx = $m[3] ? $m[3] : $m[4]; $var = $m[5]; // handle foo=(...) if (preg_match(SafeString::IS_SUBEXP_SEARCH, $var)) { $ret[$idx] = static::subexpression($var, $context); continue; } } } if ($context['flags']['advar'] && !preg_match("/^(\"|\\\\')(.*)(\"|\\\\')$/", $var)) { // foo] Rule 1: no starting [ or [ not start from head if (preg_match('/^[^\\[\\.]+[\\]\\[]/', $var) // [bar Rule 2: no ending ] or ] not in the end || preg_match('/[\\[\\]][^\\]\\.]+$/', $var) // ]bar. Rule 3: middle ] not before . || preg_match('/\\][^\\]\\[\\.]+\\./', $var) // .foo[ Rule 4: middle [ not after . || preg_match('/\\.[^\\]\\[\\.]+\\[/', preg_replace('/^(..\\/)+/', '', preg_replace('/\\[[^\\]]+\\]/', '[XXX]', $var))) ) { $context['error'][] = "Wrong variable naming as '$var' in $token !"; } else { $name = preg_replace('/(\\[.+?\\])/', '', $var); // Scan for invalid charactors which not be protected by [ ] // now make ( and ) pass, later fix if (preg_match('/[!"#%\'*+,;<=>{|}~]/', $name)) { if (!$context['flags']['namev'] && preg_match('/.+=.+/', $name)) { $context['error'][] = "Wrong variable naming as '$var' in $token ! If you try to use foo=bar param, you should enable LightnCandy::FLAG_NAMEDARG !"; } else { $context['error'][] = "Wrong variable naming as '$var' in $token ! You should wrap ! \" # % & ' * + , ; < = > { | } ~ into [ ]"; } } } } $var = static::getExpression($var, $context, $idx); if (is_string($idx)) { $ret[$idx] = $var; } else { $ret[$i] = $var; $i++; } } return $ret; } /** * Detect quote charactors * * @param string $string the string to be detect the quote charactors * * @return array<string,integer>|null Expected ending string when quote charactor be detected */ protected static function detectQuote($string) { // begin with '(' without ending ')' if (preg_match('/^\([^\)]*$/', $string)) { return array(')', 1); } // begin with '"' without ending '"' if (preg_match('/^"[^"]*$/', $string)) { return array('"', 0); } // begin with \' without ending ' if (preg_match('/^\\\\\'[^\']*$/', $string)) { return array('\'', 0); } // '="' exists without ending '"' if (preg_match('/^[^"]*="[^"]*$/', $string)) { return array('"', 0); } // '[' exists without ending ']' if (preg_match('/^([^"\'].+)?\\[[^\\]]*$/', $string)) { return array(']', 0); } // =\' exists without ending ' if (preg_match('/^[^\']*=\\\\\'[^\']*$/', $string)) { return array('\'', 0); } // continue to next match when =( exists without ending ) if (preg_match('/.+(\(+)[^\)]*$/', $string, $m)) { return array(')', strlen($m[1])); } } /** * Analyze a token string and return parsed result. * * @param string $token preg_match results * @param array<string,array|string|integer> $context current compile context * * @return array<boolean|integer|array> Return parsed result * * @expect array('foo', 'bar') when input 'foo bar', array('flags' => array('advar' => 1)) * @expect array('foo', "'bar'") when input "foo 'bar'", array('flags' => array('advar' => 1)) * @expect array('[fo o]', '"bar"') when input '[fo o] "bar"', array('flags' => array('advar' => 1)) * @expect array('fo=123', 'bar="45', '6"') when input 'fo=123 bar="45 6"', array('flags' => array('advar' => 0)) * @expect array('fo=123', 'bar="45 6"') when input 'fo=123 bar="45 6"', array('flags' => array('advar' => 1)) * @expect array('[fo', 'o]=123') when input '[fo o]=123', array('flags' => array('advar' => 0)) * @expect array('[fo o]=123') when input '[fo o]=123', array('flags' => array('advar' => 1)) * @expect array('[fo o]=123', 'bar="456"') when input '[fo o]=123 bar="456"', array('flags' => array('advar' => 1)) * @expect array('[fo o]="1 2 3"') when input '[fo o]="1 2 3"', array('flags' => array('advar' => 1)) * @expect array('foo', 'a=(foo a=(foo a="ok"))') when input 'foo a=(foo a=(foo a="ok"))', array('flags' => array('advar' => 1)) */ protected static function analyze($token, &$context) { $count = preg_match_all('/(\s*)([^\s]+)/', $token, $matchedall); // Parse arguments and deal with "..." or [...] or (...) or \'...\' or |...| if (($count > 0) && $context['flags']['advar']) { $vars = array(); $prev = ''; $expect = 0; $quote = 0; $stack = 0; foreach ($matchedall[2] as $index => $t) { $detected = static::detectQuote($t); if ($expect === ')') { if ($detected && ($detected[0] !== ')')) { $quote = $detected[0]; } if (substr($t, -1, 1) === $quote) { $quote = 0; } } // continue from previous match when expect something if ($expect) { $prev .= "{$matchedall[1][$index]}$t"; if (($quote === 0) && ($stack > 0) && preg_match('/(.+=)*(\\(+)/', $t, $m)) { $stack += strlen($m[2]); } // end an argument when end with expected charactor if (substr($t, -1, 1) === $expect) { if ($stack > 0) { preg_match('/(\\)+)$/', $t, $matchedq); $stack -= isset($matchedq[0]) ? strlen($matchedq[0]) : 1; if ($stack > 0) { continue; } if ($stack < 0) { $context['error'][] = "Unexcepted ')' in expression '$token' !!"; $expect = 0; break; } } $vars[] = $prev; $prev = ''; $expect = 0; continue; } elseif (($expect == ']') && (strpos($t, $expect) !== false)) { $t = $prev; $detected = static::detectQuote($t); $expect = 0; } else { continue; } } if ($detected) { $prev = $t; $expect = $detected[0]; $stack = $detected[1]; continue; } // continue to next match when 'as' without ending '|' if (($t === 'as') && (count($vars) > 0)) { $prev = ''; $expect = '|'; $stack=1; continue; } $vars[] = $t; } if ($expect) { $context['error'][] = "Error in '$token': expect '$expect' but the token ended!!"; } return $vars; } return ($count > 0) ? $matchedall[2] : explode(' ', $token); } } PK ! ,>�3m m lightncandy/README.mdnu �Iw�� PK ! �U��\ �\ Km lightncandy/HISTORY.mdnu �Iw�� PK ! v5�� � _� lightncandy/UPGRADE.mdnu �Iw�� PK ! �4b�D D m� lightncandy/LICENSE.mdnu �Iw�� PK ! s�t� � �� lightncandy/src/loader.phpnu �Iw�� PK ! �z� � @� lightncandy/src/Partial.phpnu �Iw�� PK ! E/�� � # lightncandy/src/Encoder.phpnu �Iw�� PK ! sx�pR� R� - lightncandy/src/Compiler.phpnu �Iw�� PK ! +���+ �+ ˣ lightncandy/src/Context.phpnu �Iw�� PK ! �'Ɉ� � �� lightncandy/src/SafeString.phpnu �Iw�� PK ! �˶(l l � lightncandy/src/Expression.phpnu �Iw�� PK ! Dm�� � �� lightncandy/src/Flags.phpnu �Iw�� PK ! �2/e e � lightncandy/src/LightnCandy.phpnu �Iw�� PK ! T��[�x �x � lightncandy/src/Runtime.phpnu �Iw�� PK ! wm} } ʏ lightncandy/src/Token.phpnu �Iw�� PK ! ��*�(� (� �� lightncandy/src/Validator.phpnu �Iw�� PK ! �Q�$ �$ 7 lightncandy/src/Exporter.phpnu �Iw�� PK ! C��-�v �v 9\ lightncandy/src/Parser.phpnu �Iw�� PK Y �
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0.04 |
proxy
|
phpinfo
|
ÐаÑтройка